我写 CI/CD 遇到的几个问题和项目结构
文章目录
- 概述
- 不同 executor 有什么不同
- 需要注意的点
- 关于环境变量
- 问题描述及解决方案
- dind 问题
- fatal: git fetch-pack: expected shallow list
- 样例
- 项目 1
- 项目 2
- 项目 3
概述
项目即将交付测试,最近将项目发布分为了测试环境和开发环境。主要逻辑是把代码打包成镜像(image),然后使用 docker-compose 部署。
整个 CI/CD 的过程基于 gitlab 完成。使用到的 executor 是 docker 和 shell 两种形式。
前端项目中,使用 docker executor 构建整个项目,然后使用 shell executor 的形式将构建产物放到 nginx 对应路径下。
后端项目一(基于 NodeJS,服务端渲染,gulp 构建前端),使用 docker executor 构建,然后使用 docker 部署到服务端。
后端项目二(基于 ts-node),没有构建,直接使用 docker executor 部署到服务器。
不同 executor 有什么不同
gitlab runner 的工作流程大致如下
tag 匹配 runner --> runner 启动 --> 启动 executor --> 初始化环境变量 -> 拉取代码 --> 执行 脚本 --> 打包并上传 artifacts --> 清理工作环境
不同之处就在于 启动 executor。如果使用的是 docker,则 runner 会按照 image
指令指定的镜像初始化一个 container,然后在这个 container 中执行后续步骤。
如果使用的是 ssh,则 runner 会根据配置参数连接到指定设备,然后执行后续步骤。
如果使用的是 shell,则相当于打开了一个终端。在这个终端环境执行后续步骤。
需要注意的点
不同的 executor 中,除了 ssh 外,其他形式的 executor 都是使用 gitlab-runner
用户(组)执行代码。
所以在使用 shell executor 时要格外注意权限问题。如果在 shell 中使用到了 docker,建议安装高版本的 dockers(支持 rootless 模式)。把 gitlab-runner
添加到 docker
用户组。在脚本的 before_script
中使用 newgrp docker
将当前用户默认组切换为 docker 组,这样就可以直接在当前用户使用 dockers,无需 root 权限。
关于环境变量
环境变量的管理分为三个层级:gitlab、.gitlab-ci.yml、docker-compose.yml。
公共的全局性参数可以在 gitlab 中设置和管理,如:harbor 的用户名、密码、地址等。
在 .gitlab-ci.yml 中可以根据需要在不同层级设置环境变量。范围从大到小依次为:全局(default、variables)、workflow、job。具体使用可以参照最后的样例。
docker-compose.yml 本身可以使用外部的环境变量。例如外部环境有一个环境变量记录了镜像名称(IMAGE_TAG),则在 docker-compose.yml 中可以使用这种方式指定镜像名称:image: $IMAGE_TAG
。具体使用可以参照最后的样例
问题描述及解决方案
dind 问题
报错信息如下:
error during connect: Get "http://docker:2375/v1.24/images/json": dial tcp: lookup docker on 10.0.6.102:53: server misbehaving
当使用 docker executor 时,需要把宿主机中的 docker、sock 挂载到 executor 中,否则 docker executor 中 docker 相关功能不能正常使用。
fatal: git fetch-pack: expected shallow list
目前没有定位到具体原因。出现这个问题是在这样一个场景:构建使用 docker,部署使用 shell。在部署时发生该错误。
最后的解决方法是,在 after_script
中每次删除源代码(rm -rf ../../*
)。
样例
项目 1
default:
interruptible: false
variables:
LATEST_TAG: "itools-harbor.example.com/simulation/nplatform-core:${CI_COMMIT_BRANCH}_latest"
TAG: "itools-harbor.example.com/simulation/nplatform-core:${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "dev"
when: always
- if: $CI_COMMIT_BRANCH == "master"
when: always
- when: never
stages:
- build
- deploy
build-job:
stage: build
allow_failure: false
tags:
- "docker"
before_script:
- docker login -u "${HARBOR_USERNAME}" --password "${HARBOR_PASSWORD}" "${HARBOR_URL}"
script:
- docker build -t "${TAG}" -t "${LATEST_TAG}" .
- docker push "${TAG}"
- docker push "${LATEST_TAG}"
- docker image rm "${TAG}"
- docker image rm "${LATEST_TAG}"
deploy-job:
stage: deploy
tags:
- "107-shell"
allow_failure: false
dependencies:
- build-job
only:
refs:
- master
before_script:
- newgrp docker
- docker login -u "${HARBOR_USERNAME}" --password "${HARBOR_PASSWORD}" "${HARBOR_URL}"
- docker compose down
- if [ `docker image ls "${LATEST_TAG}" --format "{{.ID}}" | wc -l` -gt 0 ]; then docker image rm "${LATEST_TAG}"; fi
- export DEPLOY_ENV="prod"
script:
- docker compose up -d
deploy-job-dev:
stage: deploy
tags:
- "12-shell"
allow_failure: false
dependencies:
- build-job
only:
refs:
- dev
before_script:
- newgrp docker
- docker login -u "${HARBOR_USERNAME}" --password "${HARBOR_PASSWORD}" "${HARBOR_URL}"
- docker compose down
- if [ `docker image ls "${LATEST_TAG}" --format "{{.ID}}" | wc -l` -gt 0 ]; then docker image rm "${LATEST_TAG}"; fi
- export DEPLOY_ENV="dev"
script:
- docker compose up -d
version: "3"
services:
editor:
image: ${LATEST_TAG}
container_name: nplatform_core
ports:
- 5000:1880
restart: always
environment:
DEPLOY_ENV: ${DEPLOY_ENV}
FROM itools-harbor.example.com/common/node-nplatform:0.0.1
RUN mkdir /app
COPY . /app
WORKDIR /app
RUN npm install && npm run build
ENTRYPOINT npm run start
项目 2
default:
interruptible: false
variables:
LATEST_TAG: "itools-harbor.example.com/simulation/nplatform-filesystem:${CI_COMMIT_BRANCH}_latest"
TAG: "itools-harbor.example.com/simulation/nplatform-filesystem:${CI_COMMIT_BRANCH}_${CI_COMMIT_SHORT_SHA}"
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "master"
when: always
- if: $CI_COMMIT_BRANCH == "dev"
when: always
- when: never
stages:
- check
- build
- deploy
sonarqube-check:
stage: check
tags:
- "docker"
image:
name: sonarsource/sonar-scanner-cli:latest
entrypoint: [""]
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
cache:
key: "${CI_JOB_NAME}"
paths:
- .sonar/cache
script:
- sonar-scanner
allow_failure: false
only:
- merge_requests
- master
build-job:
stage: build
allow_failure: false
tags:
- "docker"
before_script:
- docker login -u "$HARBOR_USERNAME" --password "${HARBOR_PASSWORD}" "${HARBOR_URL}"
script:
- docker build -t "${TAG}" -t "${LATEST_TAG}" .
- docker push "${TAG}"
- docker push "${LATEST_TAG}"
- docker image rm "${TAG}"
- docker image rm "${LATEST_TAG}"
deploy-job:
stage: deploy
tags:
- "docker"
allow_failure: false
dependencies:
- build-job
only:
refs:
- master
before_script:
- docker compose down
- if [ `docker image ls "${LATEST_TAG}" --format "{{.ID}}" | wc -l` -gt 0 ]; then docker image rm "${LATEST_TAG}"; fi
- docker login -u "$HARBOR_USERNAME" --password "${HARBOR_PASSWORD}" "${HARBOR_URL}"
- export DEPLOY_ENV="prod"
script:
- docker compose up -d
deploy-job-dev:
stage: deploy
tags:
- "12-docker"
allow_failure: false
dependencies:
- build-job
only:
refs:
- dev
before_script:
- docker compose down
- if [ `docker image ls "${LATEST_TAG}" --format "{{.ID}}" | wc -l` -gt 0 ]; then docker image rm "${LATEST_TAG}"; fi
- docker login -u "$HARBOR_USERNAME" --password "${HARBOR_PASSWORD}" "${HARBOR_URL}"
- export DEPLOY_ENV="dev"
script:
- docker compose up -d
version: "3"
services:
storage:
container_name: nplatform_storage
image: $LATEST_TAG
ports:
- 5002:80
restart: always
healthcheck:
test: "wget --no-verbose --tries=1 --spider http://localhost/ops/healthy || kill 1"
interval: 2m
timeout: 10s
retries: 2
start_period: 1m
environment:
DEPLOY_ENV: ${DEPLOY_ENV}
FROM itools-harbor.example.com/common/node-nplatform:0.0.1
RUN mkdir /app
COPY ./ /app
WORKDIR /app
RUN npm install
ENTRYPOINT npm run start
项目 3
default:
interruptible: false
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "master"
when: always
- if: $CI_COMMIT_BRANCH == "dev"
when: always
- when: never
stages:
- check
- build
- deploy
sonarqube-check:
stage: check
image:
name: sonarsource/sonar-scanner-cli:latest
entrypoint: [""]
tags:
- "docker"
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
cache:
key: "${CI_JOB_NAME}"
paths:
- .sonar/cache
script:
- sonar-scanner
allow_failure: false
only:
- merge_requests
- dev
build-job:
stage: build
allow_failure: false
image: "itools-harbor.example.com/common/node-nplatform:0.0.1"
tags:
- "docker"
only:
- master
before_script:
- npm config set registry http://mirrors.example.com/repository/npm/
- npm install
script:
- npm run build:prod
artifacts:
untracked: false
expire_in: 20 mins
paths:
- ./dist
build-job-dev:
stage: build
allow_failure: false
image: "itools-harbor.example.com/common/node-nplatform:0.0.1"
tags:
- "docker"
only:
- dev
before_script:
- npm config set registry http://mirrors.example.com/repository/npm/
- npm install
script:
- npm run build
artifacts:
untracked: false
expire_in: 20 mins
paths:
- ./dist
deploy-job:
stage: deploy
tags:
- "107-shell"
allow_failure: false
dependencies:
- build-job
script:
- for c in `ls $NGINX_ROOT`; do rm -rf "${NGINX_ROOT}/${c}";done;
- cp -r ./dist/* "${NGINX_ROOT}/"
only:
refs:
- master
deploy-job-dev:
stage: deploy
tags:
- "12-shell"
allow_failure: false
dependencies:
- build-job-dev
script:
- for c in `ls $NGINX_ROOT_DEV`; do rm -rf "${NGINX_ROOT_DEV}/${c}"; done;
- cp -r ./dist/* "${NGINX_ROOT_DEV}/"
after_script:
- rm -rf ../../*
only:
- dev