剥离Gitlab仓库非业务代码文件

277 阅读7分钟

剥离Gitlab仓库中非业务代码文件

一、简介

不管使用 Jenkins、Drone CI还是 Gitlab Pipeline做 CICD 自动化,代码仓库的 根目录 总是会有一些流水线相关的配置文件。如果使用的 Git Flow不注意这个问题,会因为部署环境的不一致,这些文件在各个分支中会不一致。之后一个代码合并可就会代码冲突、合并错误,造成代码部署不到环境中。同时这些文件,对于开发者无关紧要,但是对于devops来说却是关键。所有要把这些配置文件从业务代码仓库中剥离出来,与业务代码解耦。做到开发无感知,同时又方便灵活配置。

Gitlab 从 12.6 版本开始,pipeline支持读取使用外部存储的.gitlab-ci.yml。这样就可将Gitlab 所有项目业务代码仓库中的容器化配置、k8s部署配置等非业务文件剥离出来,按照目录层级方式进行区分,放在一个静态文件服务器中,或者作为一个单独的应用代码仓库,放在到 Gitlab中,通过 Pipeline 脚本流程部署到 k8s 中的 Apache2服务容器中。配置好访问地址或域名,在其他业务代码仓库cicd脚本流程中需要的地方使用wget进行下载自己的 CICD 脚本文件或容器化配置。

统一存储管理的优点:

  • 可以快速集中修改
  • 不与业务代码耦合,防止各分支中这些文件差异引起的代码冲突。
  • 所有文件进行git版本管理,变更有历史追溯

统一存储管理防单点故障及安全措施

  • 跟普通业务代码应用仓库一样进行容器化部署
  • 无状态,使用K8s部署多个实例
  • 设置用户名密码访问下载,在Gitlab 保存密码(密码设置成保密隐藏变量)
  • 限制 IP 地址段访问下载

二、流水线配置文件中心的部署

以下示例操作时,是将流水线配置文件代码仓库部署到 k8s中 apace2容器中,统称为:流水线配置文件中心

1. 目录层级

 -- docker
   |-- config.sh
   |-- Dockerfile
   |-- k8s-application.tpl.yaml
 -- .gitlab-ci.yml
 -- application
     -- app1
       |-- .gitlab-ci.yml            
       |-- test
          |-- Dockerfile
          |-- k8s-deploymen.tpl.yaml
       |-- stg
          |-- Dockerfile
          |-- k8s-deploymen.tpl.yaml
       |-- prod
          |-- Dockerfile
          |-- k8s-deploymen.tpl.yaml
     -- app2
       |-- .gitlab-ci.yml
       |-- test
          |-- Dockerfile
          |-- k8s-deploymen.tpl.yaml
       |-- stg
          |-- Dockerfile
          |-- k8s-deploymen.tpl.yaml
       |-- prod
          |-- Dockerfile
          |-- k8s-deploymen.tpl.yaml
     ....

2. docker/k8s-application.tpl.yaml

模板中的变量会在该仓库的Gitlab CICD 过程中提供。然后使用 config.sh替换该模板文件中的预置变量,生成最终能应用到 k8s 集群的文件

  • CI_PROJECT_NAME:是该仓库的仓库名
  • IMAGE_NAME: CICD 脚本命令拼成的镜像名
  • POD_NUM: 在 k8s中该应用的部署个数
 apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: $CI_PROJECT_NAME
   namespace: tools
   labels:
     app: $CI_PROJECT_NAME
   annotations:
     kubernetes.io/change-cause: $IMAGE_NAME
 spec:
   selector:
     matchLabels:
       app: $CI_PROJECT_NAME
   replicas: $POD_NUM
   template:
     metadata:
       labels:
         app: $CI_PROJECT_NAME
     spec:
       imagePullSecrets:
          - name: registry-pull-secret
       volumes:
         - name: apache2-80-conf
           configMap:
             name: $CI_PROJECT_NAME
             items:
             - key: apache2-80.conf
               path: 000-default.conf
         - name: apache2-80-auth-file
           configMap:
             name: $CI_PROJECT_NAME
             items:
             - key: apache2-80-auth-file
               path: .htpasswd
       containers:
         - name: app
           image: $IMAGE_NAME
           imagePullPolicy: IfNotPresent
           env:
             - name: K8S_POD_UID
               valueFrom:
                 fieldRef:
                   fieldPath: metadata.uid
           volumeMounts:
             - name: apache2-80-conf
               mountPath: /etc/apache2/sites-enabled/000-default.conf
               subPath: 000-default.conf
             - name: apache2-80-auth-file
               mountPath: /etc/apache2/.htpasswd
               subPath: .htpasswd
           ports:
             - containerPort: 80
               protocol: TCP
               name: web
           livenessProbe:
             failureThreshold: 3
             initialDelaySeconds: 10
             periodSeconds: 10
             successThreshold: 1
             tcpSocket:
               port: 80
             timeoutSeconds: 2
           readinessProbe:
             failureThreshold: 3
             initialDelaySeconds: 10
             periodSeconds: 10
             successThreshold: 1
             timeoutSeconds: 2
             tcpSocket:
               port: 80
           resources: 
             limits:
               cpu: "500m"
               memory: 500Mi
             requests:
               cpu: "100m"
               memory: 100Mi

configmap.yaml

 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: app-docker-k8s-config
   namespace: tools
 data:
   # http base auth基础认证密码文件内容
   apache2-80-auth-file: |
     cicd-user:"该处内容填充htpasswd工具生成的密码本"
   # Apache的配置文件,配置访问下载文件需要进行http基础认证
   apache2-80.conf: |
     <VirtualHost *:80>
         ServerAdmin localhost
         DocumentRoot /var/www/html
         ErrorLog ${APACHE_LOG_DIR}/error.log
         CustomLog ${APACHE_LOG_DIR}/access.log combined
 ​
         <Directory "/var/www/html">
             AuthType Basic
             AuthName "Restricted Content"
             AuthUserFile /etc/apache2/.htpasswd
             Require valid-user
         </Directory>
     </VirtualHost>

3. docker/Dockerfile

 FROM apache2:latest
 COPY applications /var/www/html
 RUN echo "$CI_COMMIT_SHORT_SHA" >/var/www/html/index.html

4. docker/config.sh

该脚本用于替换 Gitlab Runner 中的变量到模板文件预置的变量Key中

 #!/bin/sh
 ​
 if [ ! -n "$1" ]; then
   echo 'must input file name '
   exit 1
 else
   cat $1 |
     sed 's/$NAMESPACE'"/$NAMESPACE/g" |
     sed 's~$IMAGE_NAME'"~$IMAGE_NAME~g" |
     sed 's~$APPENV'"~$APPENV~g" |
     sed 's~$POD_NUM'"~$POD_NUM~g" |
     sed 's~$CI_PROJECT_NAME'"~$CI_PROJECT_NAME~g" |
     cat -
 fi
 exit 0

5. .gitlab-ci.yml

该应用的部署自动化Gitlab CICD脚本,脚本中的部分变量是在 Gitlab 通用 CICD 变量中保存的(设置的是值隐藏保密模式:会在输出时以星号代替)

  • HARBOR_TOKEN:保存的是Harbor 拉取镜像账号的 Token
  • PIPELINE_DINGDING_ROBOT_TOKEN:保存的是该应用部署失败时,发送钉钉通知机器人的Webhook Token
 image: alpine:with-dockerctl
 ​
 variables:
   DOCKER_HOST: tcp://localhost:2375
   DOCKER_DAEMON: |
     {
       "registry-mirrors": ["https://12345.mirror.aliyuncs.com"],
       "insecure-registries": ["harbor.curiouser.com"]
     }
 services:
   - name: docker:18.09-dind
     command:
       - /bin/sh
       - -c
       - |
         mkdir -p /etc/docker || exit
         echo "$DOCKER_DAEMON" > /etc/docker/daemon.json || exit
         dockerd-entrypoint.sh || exit
 stages:
   - build
   - deploy
 ​
 #####################################################################################
 ###                               构建阶段脚本                                       ##
 #####################################################################################
 .k8s_build: &k8s_build
   - export RELEASE_NUM="$(date +%Y%m%d)-$(echo $CI_COMMIT_SHA | cut -c1-8)"
   - export IMAGE_NAME="harbor.curiouser.com/$CI_PROJECT_NAME/$APPENV:$RELEASE_NUM"
   - docker login harbor.curiouser.com -u gitlab-ci -p $HARBOR_TOKEN
   - docker build -t "harbor.curiouser.com/$CI_PROJECT_NAME/$APPENV:$RELEASE_NUM" . -f docker/Dockerfile
   - docker push "harbor.curiouser.com/$CI_PROJECT_NAME/$APPENV:$RELEASE_NUM"
 #####################################################################################
 ###                                 部署阶段脚本                                     ##
 #####################################################################################
 .k8s_deploy: &k8s_deploy
   - export RELEASE_NUM="$(date +%Y%m%d)-$(echo $CI_COMMIT_SHA | cut -c1-8)"
   - export IMAGE_NAME=harbor.curiouser.com/$CI_PROJECT_NAME/$APPENV:$RELEASE_NUM
   - cd docker && ./config.sh ./k8s-application.tpl.yaml > k8s-application.yaml
   - cat k8s-application.yaml
   - kubectl apply -f k8s-application.yaml
   - kubectl -n $NAMESPACE rollout status --timeout=100s deployment/$CI_PROJECT_NAME || exit_code=$?
   - |
     if [ $exit_code -ne 0 ];then
       apk add curl jq --no-cache > /dev/null;
       ROLLBACK_ID=$(kubectl -n $NAMESPACE rollout undo deployment/$CI_PROJECT_NAME -ojson | jq -r '.status.observedGeneration') ;
       curl -s https://oapi.dingtalk.com/robot/send?access_token="$PIPELINE_DINGDING_ROBOT_TOKEN" -H 'Content-Type: application/json' -d '{"msgtype": "markdown","markdown": {"title": "Gitlab流水线部署失败","text": "['$CI_PROJECT_NAME']('$CI_PROJECT_URL'/-/tree/'$CI_BUILD_REF_NAME')的'$APPENV'环境第['$CI_PIPELINE_ID']('$CI_PIPELINE_URL')号流水线'$CI_JOB_STAGE'阶段失败,已回滚至最近一个稳定版本'$ROLLBACK_ID',请检查相关错误!"},"at": {"isAtAll": true}}' > /dev/null;
       exit 1;
     fi
 ​
 #####################################################################################
 #                                     CICD    触发条件                               #
 #  在 master 分支代码有代码表更时触发,将构建、部署阶段脚本任务分配至 stg-k8s-runner 上运行   #
 #     同时设置了一系列环境变量,设置了最后部署阶段要手动触发                                 #
 #####################################################################################
 build-prd:
   stage: build
   variables:
     APPENV: prod
   retry:
     max: 2
     when:
       - always
   script: *k8s_build
   only:
     - master
   tags:
     - stg-k8s-runner
 ​
 deploy-prd:
   stage: deploy
   image: harbor.curiouser.com/kubectl-stg:latest
   variables:
     NAMESPACE: tools
     APPENV: prod
     POD_NUM: 2
   script: *k8s_deploy
   only:
     - master
   when: manual
   tags:
     - stg-k8s-runner

6. 配置域名(可选)

在 k8s中的设置一个 Ingress 域名,域名绑定内网IP地址至内网DNS ,确保 CICD 过程中 Pipeline 所在环境(容器或服务器) 能访问到。方法有很多种,配置Ingress域名只是一种方法。

三、如何使用?

其他代码仓库中如何在自己的 CICD 过程中从流水线配置文件中心下载自己的配置文件

1、gitlab 仓库 设置

http://gitlab-ci:APPK8SCONFIGCENTER@appk8sconfigcenter.curiouser.com/APP_K8S_CONFIG_CENTER@app-k8s-config-center.curiouser.com/CI_PROJECT_NAME/.gitlab-ci.yml

2、Gitlab pipeline配置文件

自动化配置文件中心代码仓库中的:applicatlion/app1/.gitlab-ci.yml

 image: php:7.4-alpine-with-dockerctl
 variables:
   DOCKER_HOST: tcp://localhost:2375
   DOCKER_DAEMON: |
     {
       "registry-mirrors": ["https://12345.mirror.aliyuncs.com"],
       "insecure-registries":["harbor.curiouser.com"]
     }
 ​
 services:
   - name: docker:18.09-dind
     command:
       - /bin/sh
       - -c
       - |
         mkdir -p /etc/docker || exit
         echo "$DOCKER_DAEMON" > /etc/docker/daemon.json || exit
         dockerd-entrypoint.sh || exit
 stages:
   - scan-code
   - build
   - deploy
   
 scan-code:
   stage: scan-code
   image:
     name: harbor.curiouser.com/sonarscanner-cli:4.3.0.2102
     entrypoint: [""]
   variables:
     GIT_DEPTH: 0
   script:
     - sonar-scanner
         -Dsonar.host.url=http://sonarqube.curiouser.com
         -Dsonar.login=$SONARSCANNER_TOKEN
         -Dsonar.qualitygate.wait=true
         -Dsonar.qualitygate.timeout=500
         -Dsonar.projectName=$CI_PROJECT_NAME
         -Dsonar.projectKey=$CI_PROJECT_NAME
         -Dsonar.projectVersion=$CI_COMMIT_SHA
         -Dsonar.gitlab.project_id=$CI_PROJECT_ID
         -Dsonar.gitlab.commit_sha=$CI_COMMIT_SHORT_SHA
         -Dsonar.gitlab.ref_name=$CI_COMMIT_REF_NAME
         -Dsonar.links.scm=$CI_PROJECT_URL
         -Dsonar.links.ci=$CI_PIPELINE_URL
   rules:
     - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == 'release'
   tags:
     - stg-k8s-runner
 ​
 ############################### K8s build scripts ###########################################################
 ​
 .k8s_build: &k8s_build_scripts
   - sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories  && apk update && apk add --no-cache wget git
   - wget -q -nv -np -r -l=1 -nd --http-user=gitlab-ci --http-password=$APP_K8S_CONFIG_CENTER -R "index.html*" -P docker http://app-k8s-config-center.curiouser.com/$CI_PROJECT_NAME/$APPENV/
   - export RELEASE_NUM="$(date +%Y%m%d)-$(echo $CI_COMMIT_SHA | cut -c1-8)"
   - composer install --ignore-platform-reqs --no-dev --no-interaction -o
   - docker login harbor.curiouser.com -u gitlab-ci -p $HARBOR_TOKEN
   - docker build -t "harbor.curiouser.com/$CI_PROJECT_NAME/$APPENV:$RELEASE_NUM" . -f docker/Dockerfile
   - docker push "harbor.curiouser.com/$CI_PROJECT_NAME/$APPENV:$RELEASE_NUM"
 ​
 ############################### K8s Deploy scripts ###########################################################
 .k8s_deploy: &k8s_deploy_scripts
   - export RELEASE_NUM="$(date +%Y%m%d)-$(echo $CI_COMMIT_SHA | cut -c1-8)"
   - export IMAGE_NAME=harbor.curiouser.com/$CI_PROJECT_NAME/$APPENV:$RELEASE_NUM
   - sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories  && apk update && apk add --no-cache wget git
   - wget -q -nv -np -r -l=1 -nd --http-user=gitlab-ci --http-password=$APP_K8S_CONFIG_CENTER -R "index.html*" -P docker http://app-k8s-config-center.curiouser.com/$CI_PROJECT_NAME/$APPENV/
   - chmod +x docker/config.sh && cd docker && ./config.sh ./k8s-deployment.tpl.yaml > k8s-application.yaml
   - ./config.sh ./k8s-cronjob.tpl.yaml > k8s-cronjob.yaml
   - cat k8s-application.yaml
   - kubectl apply -f k8s-application.yaml
   - kubectl -n $NAMESPACE rollout status --timeout=280s deployment/$CI_PROJECT_NAME || exit_code=$?
   - |
     if [ $exit_code -ne 0 ];then
       apk add curl jq --no-cache > /dev/null;
       ROLLBACK_ID=$(kubectl -n $NAMESPACE rollout undo deployment/$CI_PROJECT_NAME -ojson | jq -r '.status.observedGeneration') ;
       curl -s https://oapi.dingtalk.com/robot/send?access_token="$PIPELINE_DINGDING_ROBOT_TOKEN" -H 'Content-Type: application/json' -d '{"msgtype": "markdown","markdown": {"title": "Gitlab流水线部署失败","text": "['$CI_PROJECT_NAME']('$CI_PROJECT_URL'/-/tree/'$CI_BUILD_REF_NAME')的'$APPENV'环境第['$CI_PIPELINE_ID']('$CI_PIPELINE_URL')号流水线'$CI_JOB_STAGE'阶段失败,已回滚至最近一个稳定版本'$ROLLBACK_ID',请检查相关错误!"},"at": {"isAtAll": true}}' > /dev/null;
       exit 1;
     fi
 ##################################### 测试环境构建部署触发条件 #####################################
 test_build:
   stage: build
   variables:
     APPENV: test
   script: *k8s_build_scripts
   only:
     - develop
   tags:
     - stg-k8s-runner
 test_deploy:
   stage: deploy
   image: harbor.curiouser.com/kubectl-stg:latest
   variables:
     NAMESPACE: application-test
     APPENV: test
     POD_NUM: 1
   script: *k8s_deploy_scripts
   only:
     - develop
   tags:
     - stg-k8s-runner
 ##################################### 测试环境构建部署触发条件 #####################################
 ##################################### STG环境构建部署触发条件 #####################################
 staging_build:
   stage: build
   variables:
     APPENV: stg
   script: *k8s_build_scripts
   only:
     - release
   tags:
     - stg-k8s-runner
 staging_deploy:
   stage: deploy
   image: harbor.curiouser.com/kubectl-stg:latest
   variables:
     NAMESPACE: application-stg
     APPENV: stg
     POD_NUM: 1
   script: *k8s_deploy_scripts
   only:
     - release
   tags:
     - stg-k8s-runner
 ##################################### STG环境构建部署触发条件 #####################################
 ##################################### 生产环境构建部署触发条件 #####################################
 prod_build:
   stage: build
   variables:
     APPENV: prod
   script: *k8s_build_scripts
   only:
     - tags
   except:
     - release
     - develop
   tags:
     - prod-k8s-runner
 prod_deploy:
   stage: deploy
   image: harbor.curiouser.com/kubectl-prod:latest
   variables:
     NAMESPACE: application-prd
     APPENV: prod
     POD_NUM: 3
   script: *k8s_deploy_scripts
   when: manual
   only:
     - tags
   except:
     - release
     - develop
   tags:
     - prod-k8s-runner
 ##################################### 生产环境构建部署触发条件 #####################################

四、疑问

1. 所有配置文件都放到 Gitlab 仓库里,为什么不直接使用,还要放到Apache2中?

Gitlab Pipeline 支持git 协议拉取其他仓库中的流水线配置文件,但是使用 git获取,就会拉取流水线配置文件中心中所有的项目流水线文件(使用 git的sparsecheckout比较麻烦)。不安全,也不够快。同时也无法完美解决谁有权限拉取的问题。

2. 为什么用Apache2,而不用 Nginx ?为什么用 wget,不用 curl ?

当时同时测试了Nginx和 Apache2。但是忘了 Nginx 是因为什么原因没有用。有在测试时遇到问题的或者我想起了,评论区见。

curl命令无法配置成下载整个文件夹

五、附录

1、wget命令参数详解

 wget -q -nv -np -r -l=1 -nd --http-user=cicd-user --http-password=$APP_K8S_CONFIG_CENTER_TOKEN -R "index.html*" -P docker http://app-k8s-config-center.curiouser.com/test-project/test/
 ​
 # -q 静默下载,不显示输出
 # -nv, --no-verbose 关闭详尽输出,但不进入安静模式。
 # -np, --no-parent 不追溯至父目录。
 # -r 指定递归下载
 # -l 最大递归深度( inf 或 0 代表无限制,即全部下载)。
 # -nd, --no-directories 不创建服务器上文件所在的目录路径
 # -P 指定文件存储目录 
 # -R 指定不下载的文件列表,逗号分隔
 # --http-user=USER        设置 http 用户名
 # --http-password=PASS    设置 http 密码

2、Gitlab CICD Pipeline详解

参考本人个人博客:gitbook.curiouser.top/origin/gitl…

参考

声明

  1. 创作不易,转载请附上原文出处链接及本声明。

  2. 本文章同时会发布至个人博客站点。

  3. 如果本文章能帮到你,请至个人博客GitHub 仓库点个 Star,或者 Buy me a Coffee。

本文正在参加「金石计划」