gitlab
Gitlab CI yml 較好的寫法
Table Of Content
目前 CI/CD Repo 已有套用了模組化和分專案的寫法
-
module 資料夾的用法將共用性質比較高的動作使用模組的方式管理 如:rules 的管理、build image、Cloud Run 部署、GKE 部署、CI_COMMIT_SHA 轉換
-
project 資料夾的用法將組別內的 gitlab-ci.yml 檔案統一管理,repo 中只需 Include 即可
-
善用 gitlab variable
- 很常會遇到相同的 script 只是因為 環境不同 導致需要將相同的 script copy & paste
- 可以將相同的 script 使用 function 包起來,在每個獨立的 Job 內使用 variable 帶進去即可 解決重複問題
-
CI Job 命名規則提供參考:'CI Stages 名稱-CI Job 對應環境-CI Job 描述'
- build-env-*
- deploy-env-*
-
之前的做法,會發現有很多指令都是重複性比較高的
之前的寫法
# 執行步驟
stages:
- "lint"
- "unit_test"
- "build"
- "deploy"
- "integration_test"
# 需要引入的檔案
include:
- "/variables.yml"
# lint 檢查
vuelint:
stage: "lint"
image: ${IMAGE}
tags: # gitlab-runner tag
- ${RUNNER_TAG}
script: # 填寫:單元測試需要執行的指令
- echo "run lint check script"
# 單元測試
unit-test:
stage: "unit_test"
image: ${IMAGE}
tags: # gitlab-runner tag
- ${RUNNER_TAG}
script: # 填寫:單元測試需要執行的指令
- echo "run unit-test script"
# 整合測試提供給 SR 團隊觸發
integration-test:
stage: "integration_test"
image: ${IMAGE}
tags:
- ${RUNNER_TAG}
script: # 填寫:需要執行的指令
- echo "run integration test script"
rules: # 需要加上這行規則,加上了才可以讓 SR team 觸發。這行規則會在 sprint 的時候觸法做整合測試
- if: $CI_COMMIT_BRANCH =~ "/^sprint-/" || $CI_PIPELINE_SOURCE == "pipeline"
build-dev:
stage: "build"
image: ${IMAGE}
needs: ["unit-test"] # needs 的用法會是上一個 job 完成,在執行這個 Jobs
tags: # gitlab-runner tag
- ${RUNNER_TAG}
script: # 填寫:部署環境需要執行的指令
- echo "build docker images only"
rules: # regular expression
- if: $CI_COMMIT_BRANCH =~ "/^feature-/"
build-demo:
stage: "build"
image: ${IMAGE}
needs: ["unit-test"]
tags: # gitlab-runner tag
- ${RUNNER_TAG}
script: # 填寫:部署環境需要執行的指令
- echo "build docker images only"
rules:
- if: $CI_COMMIT_BRANCH =~ "/^qa-/"
build-production:
stage: "build"
image: ${IMAGE}
needs: ["unit-test"]
tags: # gitlab-runner tag
- ${RUNNER_TAG}
script: # 填寫:部署環境需要執行的指令
- echo "build docker images only"
rules:
- if: $CI_COMMIT_BRANCH =~ "/^production-/"
deploy-dev:
stage: "build"
image: ${IMAGE}
needs: ["dev-build"] # needs 的用法會是上一個 job 完成,在執行這個 Jobs
tags: # gitlab-runner tag
- ${RUNNER_TAG}
script: # 填寫:部署環境需要執行的指令
- echo "Deploy docker images only"
rules: # regular expression
- if: $CI_COMMIT_BRANCH =~ "/^feature-/"
deploy-demo:
stage: "build"
image: ${IMAGE}
needs: ["unit-test"]
tags: # gitlab-runner tag
- ${RUNNER_TAG}
script: # 填寫:部署環境需要執行的指令
- echo "Deploy docker images only"
rules:
- if: $CI_COMMIT_BRANCH =~ "/^qa-/"
deploy-production:
stage: "build"
image: ${IMAGE}
needs: ["unit-test"]
tags: # gitlab-runner tag
- ${RUNNER_TAG}
script: # 填寫:部署環境需要執行的指令
- echo "Deploy docker images only"
rules:
- if: $CI_COMMIT_BRANCH =~ "/^production-/"
build-it:
stage: "build"
image: ${IMAGE}
needs: ["unit-test"]
tags: # gitlab-runner tag
- ${RUNNER_TAG}
script: # 填寫:部署環境需要執行的指令
- echo "build docker images only"
rules:
- if: $CI_COMMIT_BRANCH =~ "/^sprint-/"
deploy-it:
stage: "build"
image: ${IMAGE}
needs: ["it-build"]
tags: # gitlab-runner tag
- ${RUNNER_TAG}
script: # 填寫:部署環境需要執行的指令
- echo "Deploy docker images only"
rules:
- if: $CI_COMMIT_BRANCH =~ "/^sprint-/"
將重讀性的 (High) 的指令使用 module 包起來
如:rules 的管理、build image 的做法、Cloud Run 部署、GKE 部署、CI_COMMIT_SHA 轉換
CI_COMMIT_SHA 轉換
.get-commit-sha: convert-time: - export COMMITSHA=$(git show -s --format=%ct $CI_COMMIT_SHA)
rules 的管理
.reference: rules: # DEV build_image_dev: - if: $CI_COMMIT_BRANCH =~ "/^feature-/" deploy_dev: - if: $CI_COMMIT_BRANCH =~ "/^feature-/" # QA build_image_qa: - if: $CI_COMMIT_BRANCH =~ "/^qa-/" deploy_qa: - if: $CI_COMMIT_BRANCH =~ "/^qa-/" # PROD build_image_prod: - if: $CI_COMMIT_BRANCH =~ "/^production-/" deploy_prod: - if: $CI_COMMIT_BRANCH =~ "/^production-/" && $CI_PIPELINE_SOURCE == "web" when: manual - if: $CI_PIPELINE_SOURCE == "pipeline" when: never # IT(pending) rules_of_integration_test: - if: $CI_COMMIT_BRANCH =~ "/^sprint-pending/" || $CI_PIPELINE_SOURCE == "web" build_image_integration_test: - if: $CI_COMMIT_BRANCH =~ "/^sprint-pending/" deploy_integration_test: - if: $CI_COMMIT_BRANCH =~ "/^sprint-pending/"
build image 管理
include: - local: /module/jobs-convert-time.yml .build-image : gcp-cloud-build: - !reference [.get-timestamp, convert-time] - gcloud builds submit --region=${REGION} --config=cloudbuild.yaml . --substitutions=_CLOUD_IMAGE_URL="${REGISTRY_REGION}/${RPA_PROJECT_ID}/${REGISTRY_FOLDER}/${REGISTRY_FILE}:$IMAGE_ENV_$TIMESTAMP",_CLOUD_RUN_SVC_NAME="${CLOUD_RUN_SVC}",_REGION=${REGION} docker-ci: - !reference [.get-timestamp, convert-time] - echo "TIMESTAMP=>$TIMESTAMP" - echo "TIMESTAMP=>"$TIMESTAMP - gcloud auth configure-docker ${REGISTRY_REGION} - docker build -t "$REGISTRY_FILE":"$IMAGE_ENV"_"$TIMESTAMP" . - echo "\n檢視本地镜像" - docker images - echo "\n标记本地镜像" - docker tag "$REGISTRY_FILE":"$IMAGE_ENV"_"$TIMESTAMP" ${REGISTRY_REGION}/${RPA_PROJECT_ID}/"$REGISTRY_FOLDER"/"$REGISTRY_FILE":"$IMAGE_ENV"_"$TIMESTAMP" - echo "\n上傳至gcp..." - docker push ${REGISTRY_REGION}/${RPA_PROJECT_ID}/"$REGISTRY_FOLDER"/"$REGISTRY_FILE":"$IMAGE_ENV"_"$TIMESTAMP" - docker rmi ${REGISTRY_REGION}/${RPA_PROJECT_ID}/"$REGISTRY_FOLDER"/"$REGISTRY_FILE":"$IMAGE_ENV"_"$TIMESTAMP" - docker rmi "$REGISTRY_FILE":"$IMAGE_ENV"_"$TIMESTAMP"
Cloud Run 部屬
include: - local: /module/jobs-convert-time.yml .gcp-deploy: gcp-cloud-run: - !reference [.get-timestamp, convert-time] - gcloud run deploy "$CLOUD_RUN_SVC" --image=${REGISTRY_REGION}/${PROJECT_ID}/"$REGISTRY_FOLDER"/"$REGISTRY_FILE":"$IMAGE_ENV"_"$TIMESTAMP" --region=${REGION} --service-account=${SA_ID}
GKE 部屬
- GKE 部署有分成使用原生 kubectl 和 helm charts 的部署方式。
- 會有兩種部署方式,會比較容易應付不同的 repo 部署。
.template-deployment-gke: script: - export TIMESTAMP=$(git show -s --format=%ct $CI_COMMIT_SHA) - gcloud container clusters get-credentials $CLUSTER --region=${CLUSTER_REGION} - kubectl get namespaces - kubectl config get-contexts - kubectl config set-context --current --namespace=$NAMESPACE - kubectl set image deployment/$DEPLOYMENT_NAME $CONTAINER_NAME=${REGISTRY_REGION}/${RPA_PROJECT_ID}/"$REGISTRY_FOLDER"/"$REGISTRY_FILE":"$IMAGE_ENV"_"$TIMESTAMP" - kubectl rollout restart deployment/$DEPLOYMENT_NAME deploy-kubectl: # check 變數 TARGET_ENV,空字串則使用 branch name - | if [ -n "$TARGET_ENV" ]; then TARGET_ENV="$TARGET_ENV"; else TARGET_ENV="$CI_COMMIT_REF_NAME"; fi - gcloud container clusters get-credentials $CLUSTER --region=${CLUSTER_REGION} - kubectl config set-context --current --namespace=$NAMESPACE - kubectl set image deployment/$DEPLOYMENT_NAME $CONTAINER_NAME=${REGISTRY_REGION}/${RPA_PROJECT_ID}/"$REGISTRY_FOLDER"/"$REGISTRY_FILE":"$IMAGE_ENV"_"$TIMESTAMP" - kubectl rollout restart deployment/$DEPLOYMENT_NAME gcloud-auth: - gcloud components install kubectl - gcloud components install gke-gcloud-auth-plugin docker-login: - gcloud auth activate-service-account --key-file=${LOCAL_RPA_SA} - gcloud auth configure-docker asia-northeast1-docker.pkg.dev - gcloud config set project ${RPA_PROJECT_ID} - gcloud container clusters get-credentials $CLUSTER --region=${CLUSTER_REGION} deploy-helm: - export TIMESTAMP=$(git show -s --format=%ct $CI_COMMIT_SHA) - kubectl config set-context --current --namespace=$NAMESPACE - helm upgrade --install "$DEPLOYMENT_NAME" oci://${REGISTRY_REGION}/${RPA_PROJECT_ID}/bi-rpa-helm/$HELM_VERSION --namespace ${NAMESPACE} --reuse-values -f helm_value/"$TARGET_ENV".yaml --set deployment.container.image=${REGISTRY_REGION}/${RPA_PROJECT_ID}/"$REGISTRY_FOLDER"/"$REGISTRY_FILE":"$TARGET_ENV"_"$TIMESTAMP" --atomic
將重讀性 (medium) 的指令使用 module 包起來 如:CI 建立 Docker Images 和部署服務的 Job。
Docker 封裝 Image 的 function
- 將重複性的 script 用 function 的方式包起來。降低維護成本
.build_docker_template: &build_docker_template ## 因為下面的 build dev & qa & production 都會使用到 所以拉出來寫避免重複 stage: "build" tags: - ${RUNNER_GCLOUD} services: - docker:dind image: name: ${GOOGLE_IMAGE} script: - !reference [.gcloud-auth, rpa-config] - echo "Copy Env File" - cp $ENV_FILE .env - echo 'VITE_ENV='$IMAGE_ENV >> .env - cat .env # Using Docker CI Build - !reference [.build-image, docker-ci] # CI Job for dev build-dev-docker: needs: ["unit-test"] <<: *build_docker_template ## 會去跑上面的.build_docker_template: &build_docker_template 部分 variables: IMAGE_ENV: "dev" REGISTRY_FOLDER : ${PLATFORM_REG_NAME} rules: # regular expression - !reference [.reference, rules, build_image_dev] # CI Job for qa build-qa-docker: needs: ["unit-test"] <<: *build_docker_template ## 會去跑上面的.build_docker_template: &build_docker_template 部分 variables: IMAGE_ENV: "qa" REGISTRY_FOLDER : ${PLATFORM_REG_NAME} rules: - !reference [.reference, rules, build_image_qa] # CI Job for prod build-production-docker: needs: ["unit-test"] <<: *build_docker_template ## 會去跑上面的.build_docker_template: &build_docker_template 部分 variables: IMAGE_ENV: "production" REGISTRY_FOLDER : ${PLATFORM_REG_NAME} rules: - !reference [.reference, rules, build_image_prod]
Trigger Cloud Run 更新 Image
.deploy_cr_template: &deploy_cr_template ## 因為下面的 deploy dev & qa & production 都會使用到 所以拉出來寫避免重複 stage: "deploy" tags: - ${RUNNER_GCLOUD} services: - docker:dind image: name: ${GOOGLE_IMAGE} script: - !reference [.gcloud-auth, rpa-config] - !reference [.gcp-deploy, gcp-cloud-run] # CI Job dev deploy-dev-gcp-cloud-run: needs: ["build-dev-docker"] <<: *deploy_cr_template ## 會去跑上面的.deploy_cr_template: &deploy_cr_template 部分 variables: IMAGE_ENV: "dev" CLOUD_RUN_SVC: "dev-vite-rpa-platform" REGISTRY_FOLDER : ${PLATFORM_REG_NAME} rules: # regular expression - !reference [.reference, rules, deploy_dev] # CI Job qa deploy-qa-gcp-cloud-run: needs: ["build-qa-docker"] <<: *deploy_cr_template ## 會去跑上面的.deploy_cr_template: &deploy_cr_template 部分 variables: IMAGE_ENV: "qa" CLOUD_RUN_SVC: "demo-rpa-pltf-ap" REGISTRY_FOLDER : ${PLATFORM_REG_NAME} rules: - !reference [.reference, rules, deploy_qa] # CI Job prod deploy-production-gcp-cloud-run: needs: ["build-production-docker"] <<: *deploy_cr_template ## 會去跑上面的.deploy_cr_template: &deploy_cr_template 部分 variables: IMAGE_ENV: "production" CLOUD_RUN_SVC: "prd-rpa-pltf-ap" REGISTRY_FOLDER : ${PLATFORM_REG_NAME} rules: - !reference [.reference, rules, deploy_prod]
GKE 更新 Docker Image & restart pod
.deploy_gke_template: &deploy_gke_template ## 因為下面的 deploy dev & qa & production 都會使用到 所以拉出來寫避免重複 stage: "deploy" tags: - ${RUNNER_GCLOUD} services: - docker:dind image: name: ${CUSTOM_GOOGLE_IMAGE} script: - !reference [.gcloud-auth, rpa-config] - !reference [.template-deployment-gke, deploy-kubectl] # CI Job dev deploy-dev-gke: needs: ["build-dev-docker"] <<: *deploy_gke_template ## 會去跑上面的.deploy_gke_template: &deploy_gke_template 部分 variables: TARGET_ENV: "dev" CLUSTER: ${DEV_CLUSTER} NAMESPACE: "bi-rpa-platform" REGISTRY_FOLDER : "bi-rpa-platform" DEPLOYMENT_NAME: "frontend-deployment" HELM_VERSION: "app-dev" rules: # regular expression - !reference [.reference, rules, deploy_dev] # CI Job qa deploy-qa-gke: needs: ["build-qa-docker"] <<: *deploy_gke_template ## 會去跑上面的.deploy_gke_template: &deploy_gke_template 部分 variables: TARGET_ENV: "qa" CLUSTER: ${QA_CLUSTER} NAMESPACE: "bi-rpa-platform" REGISTRY_FOLDER : "bi-rpa-platform" DEPLOYMENT_NAME: "frontend-deployment" HELM_VERSION: "app-qa" rules: - !reference [.reference, rules, deploy_qa] # CI Job prod deploy-production-gke: needs: ["build-production-docker"] <<: *deploy_gke_template ## 會去跑上面的.deploy_gke_template: &deploy_gke_template 部分 variables: TARGET_ENV: "production" CLUSTER: ${PROD_CLUSTER} NAMESPACE: "bi-rpa-platform" REGISTRY_FOLDER : "bi-rpa-platform" DEPLOYMENT_NAME: "frontend-deployment" HELM_VERSION: "app-production" rules: - !reference [.reference, rules, deploy_prod]
使用 Helm chart 更新 GKE
.deploy_helm_template: &deploy_helm_template ## 因為下面的 deploy dev & qa & production 都會使用到 所以拉出來寫避免重複 stage: "deploy" tags: - ${RUNNER_GCLOUD} services: - docker:dind image: name: ${CUSTOM_GOOGLE_IMAGE} script: - !reference [.gcloud-auth, rpa-config] - !reference [.template-deployment-gke, gcloud-auth] - !reference [.template-deployment-gke, docker-login] - !reference [.template-deployment-gke, deploy-helm] # CI Job dev deploy-dev-gke: needs: ["build-dev-docker"] <<: *deploy_helm_template ## 會去跑上面的.deploy_helm_template: &deploy_helm_template 部分 variables: TARGET_ENV: "dev" CLUSTER: ${DEV_CLUSTER} NAMESPACE: "bi-rpa-platform" REGISTRY_FOLDER : "bi-rpa-platform" DEPLOYMENT_NAME: "frontend-deployment" HELM_VERSION: "app-dev" rules: # regular expression - !reference [.reference, rules, deploy_dev] # CI Job qa deploy-qa-gke: needs: ["build-qa-docker"] <<: *deploy_helm_template ## 會去跑上面的.deploy_helm_template: &deploy_helm_template 部分 variables: TARGET_ENV: "qa" CLUSTER: ${QA_CLUSTER} NAMESPACE: "bi-rpa-platform" REGISTRY_FOLDER : "bi-rpa-platform" DEPLOYMENT_NAME: "frontend-deployment" HELM_VERSION: "app-qa" rules: - !reference [.reference, rules, deploy_qa] # CI Job Prod deploy-production-gke: needs: ["build-production-docker"] <<: *deploy_helm_template ## 會去跑上面的.deploy_helm_template: &deploy_helm_template 部分 variables: TARGET_ENV: "production" CLUSTER: ${PROD_CLUSTER} NAMESPACE: "bi-rpa-platform" REGISTRY_FOLDER : "bi-rpa-platform" DEPLOYMENT_NAME: "frontend-deployment" HELM_VERSION: "app-production" rules: - !reference [.reference, rules, deploy_prod]