手把手Gitlab CI/CD搭建,基于Docker+Docker Compose

1,887 阅读5分钟

手把手教你搭建Gitlab、Docker、Docker-compose CI/CD

作为一个开发人员,怎么提高自己的效率是很关键的,那么CI/CD就是其中很关键的一环,Gitlab作为一个强大的代码托管平台,也拥有强大的CI/CD功能,并且每个月都有一定的免费分钟数供大家使用,本文将基于实际项目来讲解如何搭建一个Gitlab+Docker+Docker-Compose 的完整流水线。无论你是新手还是需要此类功能进行快速交付的团队,都能从中收获不少。

环境准备

准备服务器

这里我们准备一台ubuntu服务器。需要设置SSH访问。所以用putty key generator开生成SSH公私钥。 下载安装Putty 后打开 key generator直接点击生成即可

image.png 接下来是将上边步骤生成的公钥,也就是 ssh-ras XXXXX 这一串内容复制,打开服务器远程,执行以下命令:

cd ~/.ssh
vim authorized_keys

打开文件后将上边PUTTY生成的公钥粘贴进去即可。

最后还需要将私钥导出问OpenSSH格式:

image.png

新建Gitlab项目组

首先,需要去gitlab官网注册一个账号,关于注册账号的过程就不多赘述了,相信大多数开发者都有账号。账号注册好之后,我们创建一个项目组

示例项目组

示例项目组

创建好项目组之后,我们先得配置环境变量,点击刚刚创建的Group。然后进入Settings-> CICD

打开Variables,配置以下四个环境变量,一个是远程服务器ip,一个SSH端口(默认的话就可以不用配置),SSH用户以及私钥,这个私钥就是前边步骤使用PUTTY生成后导出的OpenSSH私钥文件,直接粘贴私钥内容即可。

这样前置条件就配置好了。接下来就是配置CI/CD模板

配置CICD模板仓库

接下来在上边新建的项目组中新一个代码仓库,命名为ci_cd_template.然后直接在网页上边编辑。

我们这边以dotnet项目cicd为例 此模板仓库文件内容如下

init.yml

这个文件是初始化阶段,用来将以下传入的变量写入环境中,内容如下:

stages:
  - init

init-job:
  stage: init
  script:
    # 这里的$ 开头的就是流水线变量,可以自定义,也可以使用平台预定义的
    - echo "CI_PROJECT_NAME=$CI_PROJECT_NAME" >> init.env
    - echo "CI_PROJECT_PATH=$CI_PROJECT_PATH" >> init.env
    - echo "CI_COMMIT_HASH=$CI_COMMIT_SHA" >> init.env
    - echo "CI_USER_NAME=$GITLAB_USER_NAME" >> init.env
  # 设置工件,过期时间1小时
  artifacts:
      paths:
        - init.env
      expire_in: 1 hour

Jobs 具体的执行job

我这里将job分为三个步骤:

  • dotnet 构建和发布
  • docker镜像构建
  • 服务器部署
  1. dotnet 构建和发布

这里主要是对dotnet项目进行build和publish。前置条件是使用 mcr.microsoft.com/dotnet/sdk:8.0镜像,这个可以根据自己实际项目进行选择,

stages:
  - dotnet-build-publish

dotnet-build-publish-job:
  stage: dotnet-build-publish
  image: mcr.microsoft.com/dotnet/sdk:8.0 
  needs: 
    - job: init-job
      artifacts: true
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - ~/.nuget/packages/
      - ~/.dotnet/
  script:
    - echo "CI_PROJECT_NAME=$CI_PROJECT_NAME"
    - echo "CI_PROJECT_PATH=$CI_PROJECT_PATH"
    - echo "CI_COMMIT_HASH=$CI_COMMIT_HASH"
    - echo "CI_USER_NAME=$CI_USER_NAME"
    - echo "building project: $PROJECT_CSPROJ_PATH"
    - dotnet restore $PROJECT_CSPROJ_PATH
    # 构建入口csproj
    - dotnet build $PROJECT_CSPROJ_PATH -c Release
    # 发布项目,输出到./publish目录
    - dotnet publish $PROJECT_CSPROJ_PATH -c Release -o ./publish /p:PublishSingleFile=true
    - cd ./publish
  artifacts:
    paths:
      - ./publish
    expire_in: 1 hour

2. Docker镜像构建

这里先讲一下镜像仓库仓库,Gitlab中可以使用自带的Container Register。方便快捷,直接和流水线集成。每个代码仓库中在Deploy->Container Register下就可以看到。 那么如何构建呢,具体配置如下:

这个步骤使用的基础镜像是docker。而且必须添加服务:docker:dind才能执行docker build命令

stages:
  - docker-image-build

variables:
    # 容器注册表的登录凭据,GitLab 提供的默认变量
    IMAGE_NAME: $CI_PROJECT_PATH/$CD_IMAGE_NAME 
    # 使用分支名称作为标签
    IMAGE_TAG: "${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}" 

docker-image-build-job:
  stage: docker-image-build
  image: docker
  needs: 
    - job: dotnet-build-publish-job
      artifacts: true
  services: # 配置依赖服务
    - docker:dind
  script:
    - echo "CI_DOCKERFILE_PATH=$CI_DOCKERFILE_PATH"
    - echo "CI_REGISTRY=$CI_REGISTRY"
    - echo "IMAGE_NAME=$IMAGE_NAME"
    # 登录docker 这里都是流水线自带的变量
    - echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
    # 使用dockerfile构建进项
    - docker build -f $CI_DOCKERFILE_PATH -t $CI_REGISTRY/$IMAGE_NAME:$IMAGE_TAG .
    # 推送镜像到Container register
    - docker push $CI_REGISTRY/$IMAGE_NAME:$IMAGE_TAG
    - echo "IMAGE_TAG=$IMAGE_TAG" >> image.env
  artifacts:
      paths:
        - image.env
      expire_in: 1 hour

镜像构建完毕后会将镜像放在Container Register

  1. 服务器部署

首先,使用docker-compose 部署的话,需要指定镜像,我们每次执行流水线的时候,都会构建一个新版本,所以需要动态更新docker-compose中的image值。那么就需要指定能够进行字符串替换的基础镜像,这里使用ubuntu:latest基础镜像。

还有一个重要的点,就是需要加载SSH私钥,所以在此步骤中有个前置脚本before_script用来加载私钥。

stages:
  - digitalocean-ec-docker-deploy


variables:
    # 容器注册表的登录凭据
    IMAGE_NAME: $CI_PROJECT_PATH/$CD_IMAGE_NAME # GitLab 提供的默认变量
    IMAGE_TAG: "${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}" # 使用分支名称作为标签
    APP_NAME: $CI_APP_NAME
    # SSH 地址
    SSH_USER_AND_HOST: ${SSH_HOST_USER}@${SSH_HOST_IP}
    # $CI_REGISTRY/$IMAGE_NAME:$IMAGE_TAG


digitalocean-ec-docker-deploy-job:
  stage: digitalocean-ec-docker-deploy
  image: ubuntu:latest
  needs: 
    - job: docker-image-build-job
      artifacts: true
  # 前置脚本,加载SSH私钥文件
  before_script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval $(ssh-agent -s)
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/test.pem
    - chmod 600 ~/.ssh/test.pem

  script:
    # 备份
    - |-
        ssh -p ${SSH_HOST_PORT} -o StrictHostKeyChecking=no -i ~/.ssh/test.pem ${SSH_USER_AND_HOST} << EOF
        
        if [ ! -d /usr/apps/${APP_NAME} ]; then
          sudo mkdir /usr/apps/${APP_NAME}
        fi

        cd /usr/apps/${APP_NAME}

        if [ -f docker-compose.yml ]; then
          sudo mv docker-compose.yml docker-compose-backup.yml
        fi

        EOF

    # 复制文件到服务器
    # sudo mv /home/ubuntu/docker-compose.yml /usr/apps/${APP_NAME}/docker-compose.yml
    - scp -P ${SSH_HOST_PORT} -o StrictHostKeyChecking=no -i ~/.ssh/test.pem docker-compose-${APP_NAME}.yml ${SSH_USER_AND_HOST}:/usr/apps/${APP_NAME}/docker-compose.yml

    - |-
        ssh -p ${SSH_HOST_PORT} -o StrictHostKeyChecking=no -i ~/.ssh/test.pem ${SSH_USER_AND_HOST} << EOF

        echo "开始登录"
        echo "$CI_JOB_TOKEN" | sudo docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
        echo "开始拉去镜像:$CI_REGISTRY/$IMAGE_NAME:$IMAGE_TAG "
        sudo docker pull $CI_REGISTRY/$IMAGE_NAME:$IMAGE_TAG

        cd /usr/apps/${APP_NAME}
        # 这里就是动态替换docker-compose文件中最新的image值
        sudo sed -i "s|image: .*|image: $CI_REGISTRY/$IMAGE_NAME:$IMAGE_TAG|" docker-compose.yml

        sudo docker compose up -d

        EOF
dotnet-deploy.yml 入口文件

这里就是cicd入口配置,定义了阶段以及对应阶段的具体配置文件。并且job可以自定义,比如增加secret扫描、代码扫描、单元测试等等更加丰富的流程。

stages:
  - init
  - dotnet-build-publish
  - docker-image-build
  - digitalocean-ec-docker-deploy

include:
  - local: 'init.yml'
  - local: 'jobs/dotnet-build-publish-job.yml'
  - local: 'jobs/docker-image-build-job.yml'
  - local: 'jobs/digitalocean-ec-docker-deploy-job.yml'

以上就是整个cicd模板。原理就是,首先使用实际项目中的dockerfile进行镜像构建,然后将项目中docker-compose文件替换image值为最新的tag。在上传到服务器,然后在服务器执行 docker compose up -d命令。

配置项目

在实际项目中新建.gitlab-ci.yml文件、Dockerfile文件、Docker-Compose.yml文件 DockerfileDocker-Compose.yml文件根据自己项目的实际情况配置。

.gitlab-ci.yml文件:

我这里设置的是手动出触发。也可以使用自动触发,可以设置Rules来指定不同的触发规则。比如dev分支触发dev cicd,main分支触发生产环境cicd等。

stages:
  - manual-cicd

mvc-job:
  stage: manual-cicd
  # 手动出发
  when: manual
  variables:
    # 指定build和pubish的项目文件
    PROJECT_CSPROJ_PATH: "src/PlutoBlog.Mvc/PlutoBlog.Mvc.csproj"
    # 指定dockerfile文件
    CI_DOCKERFILE_PATH: "src/PlutoBlog.Mvc/Dockerfile"
    # 应用名称
    CI_APP_NAME: "blogmvc"
    # 镜像名称
    CD_IMAGE_NAME: "blogmvc"
    # dotnet publish的目录
    PUBLISH_DIR: "./publish"
  # 触发器,用于触发主的cicd模板仓库流水线
  trigger:
    include:
      # 指定cicd模板仓库地址
      - project: 'dotnetydd.com/ci_cd_template'
        # 指定分支
        ref: main
        # 指定入口yml文件
        file: '/dotnet-deploy.yml'

以上配置增加后,每次提交代码,都可以手动在Build->Pipelines中找到流水线

最终的执行效果如下:

本文使用 mdnice 排版