iT邦幫忙

2023 iThome 鐵人賽

DAY 6
0
DevOps

AWS ECS + Gitlab + Laravel + Terraform 從入門到摔坑系列 第 6

Day 6 用 Gitlab Pipeline deploy Docker image

  • 分享至 

  • xImage
  •  

完成經典的 Hello World,我們來做點正經事啦~(本日程式碼

把「build docker image 並 push 到 ECR repository」的工作丟給 Gitlab Pipeline 做~~,然後我們就可以去打電動/追劇惹~~~

延續昨天的 .gitlab-ci.yml ,在 deploy job 的 script 寫上 build 跟 push image 的指令們:

stages:
  - deploy

deploy:
  stage: deploy
  image: cjwind/gitlab-executor:docker-awscli-0.1.0
  variables:
    ECR_REPO_URI: $ECR_BASE/$ECR_REPO
  script:
    - cat $ENV >> .env
    - IMAGE_TAG=`date +%Y%m%d-%H%M%S`
    - docker build . -t $ECR_REPO_URI:$IMAGE_TAG
    - docker tag $ECR_REPO_URI:$IMAGE_TAG $ECR_REPO_URI:latest
    - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin $ECR_BASE
    - docker push $ECR_REPO_URI:$IMAGE_TAG
    - docker push $ECR_REPO_URI:latest
    - docker rmi $ECR_REPO_URI:$IMAGE_TAG
    - docker rmi $ECR_REPO_URI:latest

指令從 docker builddocker push 都是前面手動做過的,相信沒有太大問題。最後兩個 docker rmi 是做 image 的清除,不然 runner 上的 docker image 會越來越多、越來越多直到把硬碟塞爆,然後 pipeline job 就會出各種奇奇怪怪的 error 說它硬碟太撐了。

第二行指令是我們想以日期跟時間作為版號,所以把日期時間的字串 assign 給 IMAGE_TAG 變數,讓後面 docker build 可以上 tag。

那第一行 cat $ENV >> .env 在做什麼呢?看起來是把變數 ENV 的檔案內容附加到 .env 檔案的最後,為什麼要這麼做呢?因為 .env 裡有些敏感資訊,像是資料庫的帳密。我們不會把 .env commit 進 repository,不然各種帳號、密碼或者 api key 都會被看光光而且永存於 git history 裡,這樣很不好捏~

但是 docker image 要執行 Laravel 又需要 .env 內的資訊,怎麼辦呢?我們可以把敏感資訊寫在 Gitlab CI/CD Variable(下面會講),然後在 job script 裡把敏感資訊們 inject 進 .env

欸?那如果 docker image 整包被偷走,敏感資訊就被偷走了耶?

是的,沒錯。但這問題的解決方式是不能把敏感資訊放在 .env 內,要在執行期間透過其他方式取得,不過這情境不在本系列文的討論範圍,有興趣的朋友可以研究看看 Laravel 如何在執行期間才透過其他服務,如 AWS Secret Manager,取得需要的敏感資訊。

再來是 hello world 沒有的兩個設定:imagevariables

variables 很好猜(?),就是定義變數嘛~這邊只是把 ECR_BASEECR_REPO 兩個從 CI/CD variable 來的變數組合成 ECR_REPO_URI 方便後面使用而已。

最後是 image ,記得 register runner 的時候有指定一個預設 image 嗎?我們也可以在 job 裡用 image 指定這個 job 要使用的 image。這個 image 必須有能力執行 job script 的指令們,像這邊它要能執行 docker 跟 awscli。如果沒有現成的 image 就要自己做,這邊筆者做了一個包含 docker 以及 awscli 的 image 放在 Docker Hub 上。

在執行 job 的 container 內要能執行 docker 也有一點小眉角~~(aka 容易遇到各種問題)~~,比較簡單的方式是 register runner 時加上 --docker-volumes 將 runner 的 /var/run/docker.sock 掛載進 container,讓 container 透過 host 的 docker daemon 做事。gitlab runner register 指令的 --docker-volumes 相當於每次啟動 container 時加上 -v ,這個參數會把指定的 host(啟動 container 的機器)的路徑掛載到 container 的指定路徑,像 /var/run/docker.sock:/var/run/docker.sock 就是把 host 的 /var/run/docker.sock 掛進 container 的 /var/run/docker.sock 。利用這個小技巧,container 的 docker 指令要用這個檔案跟 docker daemon 溝通時,實際上是跟 runner 的 docker daemon 溝通(這段暫時看不懂、只知道這樣設會動也沒關係,有興趣可以把參數拿掉看看 pipeline job 會怎麼樣)。

設定 Gitlab CI/CD Variable

延續前面操作,我們到 Gitlab Settings ⇒ CI/CD ⇒ Variables 區塊 Add variable 增加 script 中用到的三個變數:

  • ECR_BASE :填 [YOUR_AWS_ACCOUNT_ID].dkr.ecr.[REGION].amazonaws.com

  • ECR_REPO :填前面開的 ECR repository 名稱,例如 my-app

  • ENV :複製 Laravel 的 .env.example 內容並填入對應需要的設定,type 選 FILE。(欸…對啦這邊筆者偷懶,直接把整份 .env 放在 CI/CD variable,也不管是不是 credential 之類的敏感資料)

另外也要設定給 awscli 用的 credential 及預設 region:

  • AWS_ACCESS_KEY_ID

  • AWS_SECRET_ACCESS_KEY

  • AWS_DEFAULT_REGION

credential 要填擁有 ecr:GetAuthorizationToken 跟 push image 權限的 IAM user 的 access key 跟 secret。預設 region 則填 ap-northeast-1

所有變數的 Environment scope 先選 All (default),如果預設有勾選 protect variable,先取消打勾。

Project 的 CI/CD variable 預設 type 是 variable,也就是一般的變數,在 job 裡用 variable key 取得 variable value 使用。

另一種 type 是 file,它由 variable key、variable value 以及一個檔案組成。在 job script 使用時 variable value 會被存在一個暫存檔裡,variable key 則是 environment variable name,environment variable 的值則是這個暫存檔的路徑,所以在 script 內要以操作檔案的方式使用 file type 的 CI/CD variable。

我們的變數 ENV 便是 file 類型,在 job script 裡 $ENV 是暫存檔的路徑,暫存檔的內容是 CI/CD variable 設定的、我們想要的 .env 檔案內容,所以操作 $ENV 要用對待檔案路徑的方式來操作,在這裡我們用 cat 把它 append 到 .env 。有興趣的朋友可以在 script echo $ENV 看看內容。

執行 pipeline

把修改 commit 並 push 上 gitlab 的 repository 來啟動 pipeline,觀察 job 的執行狀況:

https://ithelp.ithome.com.tw/upload/images/20230916/20160671Agagj8IFRQ.png

最後有 Job succeeded 就是成功了!

利用 Gitlab Environment 區分環境

最後介紹一下在 Gitlab 怎麼區分「環境」~

通常開發 web service 的開發者會有自己的開發環境。部屬出去讓一般使用者使用的是正式環境,可能稱為 production 或簡寫 prod。一般會再架設一個跟正式環境幾乎一樣的環境來做整體測試(包含應用程式的邏輯流程以及部屬),這個環境稱為 stage 或 staging。

stage 跟 prod 的差別只是上面運行的程式還在開發與測試,並且使用的 infrastructure 規模比較小(例如機器比較少或比較小台),它們擁有相同的部屬方式跟服務,我們利用 stage 來確認在 prod 環境上應用程式跟 infrastructure 的設置與運作是正常的,盡量降低東西上到 prod 才發現不會動的機率。當然 stage 終究會有些地方跟 prod 不同,例如介接第三方 API 時 stage 只能使用第三方的測試環境、不能接正式環境來測試(總不能接金流的正式環境來測試…🫠),之後 prod 上線依然要對這些部份進行測試。

既然要用相同方式 deploy prod 跟 stage 環境,我們應該用一樣的 Gitlab Pipeline 來 deploy,但是兩個環境的 .env 、credential、ECR repository 等等不會一樣啊~如何用同一份 .gitlab-ci.yml deploy 兩個「程式碼相同但設定與 deploy target 不同」的環境?

這要用到 Gitlab 的 Environment 功能啦~

一個 Environment 包含程式 deploy 到的環境的各種資訊,像是 CI/CD variable、有哪些 deployment、環境的網址等等。來看看實際上怎麼用!

Environment 功能在 Operate 底下:

https://ithelp.ithome.com.tw/upload/images/20230916/20160671u70EWPFKKW.png

建立兩個 environment 分別叫 stagingprod

https://ithelp.ithome.com.tw/upload/images/20230916/20160671B5A9yQ4STD.png

建好的 staging environment,還沒有任何 deployment:

https://ithelp.ithome.com.tw/upload/images/20230916/20160671pvVNVJS9Z6.png

再來是調整 CI/CD variable 的 environment scope 讓我們可以在不同環境使用相同變數名稱但不同值(不同 credential、env、ECR repository 等等)。點進某個 CI/CD variable 修改 environment scope:

https://ithelp.ithome.com.tw/upload/images/20230916/20160671MzaovHMoNU.png

這樣修改後,staging environment 的 pipeline 拿到的 $ECR_REPO 值會是 my-app 。新增另一個 key 也叫 ECR_REPO 的 CI/CD variable,設定 value 為 prod-app 、environment scope 為 prod ,如此 prod environment 的 pipeline 拿到的 $ECR_REPO 值會是 prod-app

最後是 .gitlab-ci.yml 內 job 的小調整,我們要讓 deploy 相關的 job 知道它要 deploy 到哪個 environment。在 job 加上 environment 這個參數:

deploy:
  stage: deploy
  environment: staging
  # ...

欸~這樣不就對每個 environment 都要寫一個 deploy job?script 會重複吧?說好的同一份 deployment script 呢?

這個問題的解決方式有很多,筆者介紹最懶惰的(喂):開兩個 branch 分別叫 stagingprod ,這兩個 branch 專門用來 deploy,所以可以利用 Gitlab 預先定義好的變數 CI_COMMIT_BRANCH 來當作 job 的 environment:

deploy:
  stage: deploy
  environment: $CI_COMMIT_BRANCH
  # ...

上一篇
Day 5 Hello Gitlab Pipeline
下一篇
Day 7 進入 ECS:建立 ECS Cluster
系列文
AWS ECS + Gitlab + Laravel + Terraform 從入門到摔坑30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言