容器 Docker 越来越受开发者和运维人员的喜爱,更是作为实践 DevOps 的一个中要工具。同时 Gitlab 提供了免费的代码管理服务,其 gitlab-ci 更是提供了强大的自动化 CI/CD 流程功能。

本文以一个静态站点的示例来说明如何使用 gitlab-ci 和 docker 进行容器镜像的构建,以及如何将镜像自动化部署到目标服务器上。

编写Dockerfile

首先在代码库中增加 Dockerfile ,用于描述如何构建应用的容器镜像。以下是一个基于 Hugo 的静态站点应用的示例:

FROM mengzyou/hugo:latest as builder

COPY . /app/

RUN hugo

FROM nginx:1.16-alpine

RUN set -x \
  && rm -f /etc/nginx/conf.d/default.conf \
  && mkdir -p /usr/share/nginx/html

COPY --from=builder /app/nginx-default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/public/ /usr/share/nginx/html

其实非常简单,使用了多阶段构建,以 mengzyou/hugo 作为构建镜像,然后将生成的静态文件拷贝到 nginx 镜像中,最终生成静态站点的镜像。

配置Gitlab-ci构建容器镜像

该阶段,在项目根目录添加 .gitlab-ci.yml 文件,示例内容如下:

variables:
  DOCKER_DRIVER: overlay2
  CI_REGISTRY_IMAGE: ${CI_REGISTRY}/mengzyou/app

before_script:
  - echo $CI_JOB_NAME
  - echo $CI_PROJECT_DIR

stages:
  - build

build:docker:
  stage: build
  variables:
    DOCKER_HOST: tcp://docker:2375
  image: docker:stable
  services:
    - docker:dind
  script:
    - echo "Building image - $CI_REGISTRY_IMAGE:latest"
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
    - docker image build --force-rm --no-cache -t $CI_REGISTRY_IMAGE:latest .
    - docker image push $CI_REGISTRY_IMAGE:latest
  only:
    - master

其中有几个变量是需要在代码库的 Gitlab 上进行配置,如下图所示:

gitlab-ci-setting

因为直接是用了 Gitlab 提供的容器镜像服务,构建完成的镜像需要推送到该镜像仓库服务,所以需要配置相应的仓库地址,用户名和登录密码:

  • CI_REGISTRY : 仓库地址,如 registry.gitlab.com
  • CI_REGISTRY_USER : Gitlab 的访问用户名
  • CI_REGISTRY_PASSWORD : Gitlab 的访问密码

这样在推送镜像之前,需要进行一次登录操作。更多关于如何是用 Gitlab 容器镜像仓库可参考 https://docs.gitlab.com/ee/user/project/container_registry.html

该 pipeline 工作使用了 Gitlab Runner 的 docker 执行器,如果是自己安装和注册 gitlab-runner ,请参考其文档。这里直接是用了 Gitlab.com 分享的 Runner 。

安装用于部署的gitlab-runner

为了在目标服务器上使用 gitlab-ci 进行自动部署(运行容器),需要在目标服务器上安装和注册 gitlab-runner 。由于目标服务器上运行容器,因此应该首先安装好 Docker 环境,同时 gitlab-runner 也直接是用容器运行(当然也可以直接本地运行,具体可以查看 gitlab-runner 的文档)。

安装注册Runner

可以使用 docker-compose 来部署 gitlab-runner 容器,在目标服务器上的 ~/gitalb/ 目录下编写如下 docker-compose.yml 文件:

version: '2.4'

services:
  runner:
    image: gitlab/gitlab-runner:alpine-v11.11.0
    container_name: gitlab_runner
    restart: always
    network_mode: bridge
    volumes:
      - ./config/:/etc/gitlab-runner/
      - /var/run/docker.sock:/var/run/docker.sock

创建目录 config 用于保存 runner 的配置:

mkdir config

然后启动 runner 容器:

docker-compose up -d runner

注册 runner 之前需要在 Gitlab 项目的 CI/CD 配置中(设置 > CI/CD > Runner)查看注册 URL 和令牌,如下图所示:

gitalb-runner-setting

接下来,运行一下命令进行 runner 的注册:

docker-compose exec runner gitlab-runner register \
  --url https://gitlab.com/ \
  --registration-token $REGISTRATION_TOKEN \
  --executor docker \
  --description "app-deployment-runner" \
  --tag-list "deploy,docker" \
  --docker-image "mengzyou/docker:19.03" \
  --docker-volumes /var/run/docker.sock:/var/run/docker.sock

注意 : 上面的 $REGISTRATION_TOKEN 需要替换成真实的令牌值。

上面的命令注册了一个默认使用 mengzyou/docker 镜像运行容器的执行器,同时挂载了 /var/run/docker.sock,因为默认执行容器里的 docker 将房屋主机上的 docker 服务。

配置Gitlab-ci进行自动部署

.gitlab-ci.yml 中增加部署的工作:

stages:
  - build
  - deploy

deoploy:docker:
  stage: deploy
  script:
    - echo "Deploy try_app - $CI_REGISTRY_IMAGE:latest"
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
    - docker rm -f try_app || true
    - docker image pull $CI_REGISTRY_IMAGE:latest
    - docker container run --name try_app -p 80:80 -d $CI_REGISTRY_IMAGE:latest
  only:
    - master
  tags:
    - deploy
    - docker

当然,也可以写部署脚本来执行部署的步骤,也可以使用 docker-compose 通过 docker-compose.yml 文件来执行,例如:

deoploy:docker:
  stage: deploy
  script:
    - echo "Deploy ${CD_COMPOSE_PROJECT}_app - $CI_REGISTRY_IMAGE:latest"
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
    - docker image pull $CI_REGISTRY_IMAGE:latest
    - mkdir -p /tmp/${CD_COMPOSE_PROJECT} && cp ${CI_PROJECT_DIR}/docker-compose.prod.yml /tmp/${CD_COMPOSE_PROJECT}/docker-compose.yml
    - cd /tmp/${CD_COMPOSE_PROJECT} && docker-compose up -d myblog
  only:
    - master
  tags:
    - deploy
    - docker

其中可以定义 CD_COMPOSE_PROJECT 变量来指定 docker-compose 的项目名,相应的在代码根目录增加 docker-compose.prod.yml 文件:

version: '2.4'

networks:
  web:
    external: true

services:
  app:
    image: "registry.gitlab.com/mengzyou/app:latest"
    networks:
      - web
    restart: unless-stopped
    mem_limit: 256M
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=web"
      - "traefik.backend=app"
      - "traefik.frontend.rule=Host:app.mengz.me;PathPrefix:/"
      - "traefik.frontend.redirect.entryPoint=https"
      - "traefik.frontend.redirect.permanent=true"
      - "traefik.port=80"

上面的 docker-composle 文件还定义了 traefik 相关的参数,这要求在目标服务器上已经运行了相应的 traefik 服务作为 Web 代理。

完成以上步骤之后,每次推送 master 分支,都会触发相应的 gitlab CI/CD pipeline 来执行每个阶段的 CI/CD 工作,如下图:

gitlab-pipeline

总结

以上仅仅进行了一个简单应用的 CI/CD 示例,主要演示了如何使用 gitlab-ci 和 docker 来进行 CI/CD 流程。

对于不同的项目,如何构建?如何部署,情况都可能不一样,需要根据不同的场景来编写 .gitlab-ci.yml 文件,需要进一步阅读相应的文档