Node.js项目CI/CD #

一、项目结构 #

text
nodejs-project/
├── src/
│   └── index.js
├── test/
│   └── index.test.js
├── Dockerfile
├── package.json
├── package-lock.json
└── .gitlab-ci.yml

二、基础配置 #

package.json #

json
{
  "name": "nodejs-project",
  "version": "1.0.0",
  "scripts": {
    "start": "node src/index.js",
    "test": "jest",
    "test:coverage": "jest --coverage",
    "lint": "eslint src/",
    "build": "webpack --mode production"
  },
  "devDependencies": {
    "eslint": "^8.0.0",
    "jest": "^29.0.0",
    "webpack": "^5.0.0"
  }
}

Dockerfile #

dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["npm", "start"]

三、完整CI/CD配置 #

yaml
stages:
  - lint
  - test
  - build
  - docker
  - deploy

variables:
  NODE_VERSION: "18"
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

.node_template:
  image: node:${NODE_VERSION}-alpine
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
  before_script:
    - npm ci --prefer-offline

lint:
  extends: .node_template
  stage: lint
  script:
    - npm run lint
  allow_failure: true

test:
  extends: .node_template
  stage: test
  script:
    - npm run test:coverage
  coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
  artifacts:
    when: always
    reports:
      junit: test-results/junit.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

test_integration:
  extends: .node_template
  stage: test
  services:
    - name: postgres:14
      alias: db
    - name: redis:7
      alias: cache
  variables:
    POSTGRES_DB: test
    POSTGRES_USER: test
    POSTGRES_PASSWORD: test
    DATABASE_URL: postgres://test:test@db:5432/test
    REDIS_URL: redis://cache:6379
  script:
    - npm run test:integration

build:
  extends: .node_template
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week

docker_build:
  stage: docker
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker pull $CI_REGISTRY_IMAGE:latest || true
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE
    - |
      if [ "$CI_COMMIT_BRANCH" == "main" ]; then
        docker tag $DOCKER_IMAGE $CI_REGISTRY_IMAGE:latest
        docker push $CI_REGISTRY_IMAGE:latest
      fi
  only:
    - main
    - develop

deploy_staging:
  stage: deploy
  image: bitnami/kubectl:latest
  environment:
    name: staging
    url: https://staging.example.com
  script:
    - kubectl config set-cluster k8s --server="$KUBE_URL" --insecure-skip-tls-verify=true
    - kubectl config set-credentials admin --token="$KUBE_TOKEN"
    - kubectl config set-context default --cluster=k8s --user=admin
    - kubectl config use-context default
    - |
      cat <<EOF | kubectl apply -f -
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: nodejs-app
        namespace: staging
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: nodejs-app
        template:
          metadata:
            labels:
              app: nodejs-app
          spec:
            containers:
            - name: nodejs-app
              image: $DOCKER_IMAGE
              ports:
              - containerPort: 3000
              env:
              - name: NODE_ENV
                value: staging
              resources:
                requests:
                  cpu: 100m
                  memory: 128Mi
                limits:
                  cpu: 500m
                  memory: 512Mi
      EOF
    - kubectl rollout status deployment/nodejs-app -n staging
  only:
    - develop

deploy_production:
  stage: deploy
  image: bitnami/kubectl:latest
  environment:
    name: production
    url: https://example.com
  script:
    - kubectl config set-cluster k8s --server="$KUBE_URL" --insecure-skip-tls-verify=true
    - kubectl config set-credentials admin --token="$KUBE_TOKEN"
    - kubectl config set-context default --cluster=k8s --user=admin
    - kubectl config use-context default
    - kubectl set image deployment/nodejs-app nodejs-app=$DOCKER_IMAGE -n production
    - kubectl rollout status deployment/nodejs-app -n production
  only:
    - main
  when: manual

四、配置详解 #

1. 模板复用 #

yaml
.node_template:
  image: node:${NODE_VERSION}-alpine
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
  before_script:
    - npm ci --prefer-offline

2. 缓存优化 #

yaml
cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/

3. 测试覆盖率 #

yaml
test:
  coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

4. Docker构建 #

yaml
docker_build:
  script:
    - docker pull $CI_REGISTRY_IMAGE:latest || true
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE

5. Kubernetes部署 #

yaml
deploy:
  script:
    - kubectl set image deployment/nodejs-app nodejs-app=$DOCKER_IMAGE
    - kubectl rollout status deployment/nodejs-app

五、高级配置 #

1. 多版本测试 #

yaml
test:
  parallel:
    matrix:
      - NODE_VERSION: [16, 18, 20]
  image: node:${NODE_VERSION}-alpine
  script:
    - npm test

2. 安全扫描 #

yaml
include:
  - template: Jobs/SAST.gitlab-ci.yml
  - template: Jobs/Dependency-Scanning.gitlab-ci.yml

dependency_scanning:
  stage: test

3. 代码质量 #

yaml
code_quality:
  stage: test
  image: docker:latest
  services:
    - docker:dind
  variables:
    CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.26"
  script:
    - docker pull --quiet "$CODE_QUALITY_IMAGE" || docker pull "$CODE_QUALITY_IMAGE"
    - |
      docker run --env SOURCE_CODE="$PWD" \
        --volume "$PWD":/code \
        --volume /var/run/docker.sock:/var/run/docker.sock \
        "$CODE_QUALITY_IMAGE" /code
  artifacts:
    reports:
      codequality: gl-code-quality-report.json

4. 自动发布 #

yaml
publish_npm:
  stage: deploy
  image: node:18-alpine
  script:
    - npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN
    - npm publish --access public
  only:
    - tags

六、环境变量配置 #

GitLab CI/CD变量 #

变量 说明
CI_REGISTRY GitLab容器镜像仓库地址
CI_REGISTRY_USER 镜像仓库用户名
CI_REGISTRY_PASSWORD 镜像仓库密码
KUBE_URL Kubernetes API地址
KUBE_TOKEN Kubernetes访问令牌
NPM_TOKEN npm发布令牌

项目变量设置 #

  1. 进入Settings → CI/CD → Variables
  2. 添加以下变量:
    • NPM_TOKEN(Masked)
    • KUBE_URL
    • KUBE_TOKEN(Masked)

七、最佳实践 #

1. 使用alpine镜像 #

yaml
image: node:18-alpine

2. 缓存依赖 #

yaml
cache:
  paths:
    - node_modules/

3. 使用artifacts传递产物 #

yaml
build:
  artifacts:
    paths:
      - dist/

4. 条件部署 #

yaml
deploy_production:
  only:
    - main
  when: manual

5. 环境管理 #

yaml
deploy_staging:
  environment:
    name: staging
    url: https://staging.example.com

八、故障排查 #

1. 依赖安装失败 #

yaml
before_script:
  - npm ci --prefer-offline || npm install

2. 测试超时 #

yaml
test:
  timeout: 30m

3. Docker构建失败 #

yaml
docker_build:
  script:
    - docker build --no-cache -t $DOCKER_IMAGE .

下一步 #

现在你已经掌握了Node.js项目CI/CD,接下来让我们学习 Docker集成

最后更新:2026-03-28