ããã«ã¡ã¯ã SRE ã® @suzuki-shunsuke ã§ãã
Pull Request (以ä¸PR) ã® CI ã® terraform plan (ä»¥ä¸ plan) ã®å®è¡çµæ(ä»¥ä¸ plan file)ã S3 ã«ä¿åãã¦ãå®å
¨ã« terraform apply (ä»¥ä¸ apply) åºæ¥ãããã«ããã¨ã¨ãã«ã
GitHub ãªãã¸ããªã® Require branches to be up to date before merging ã®è¨å®ãç¡å¹åãããã¨ã§ Experience ãåä¸ãã話ãç´¹ä»ãã¾ãã
課é¡
Terraform ã® CI/CD ã CodeBuild ã«ç§»è¡ãã話 ã§ãç´¹ä»ããããã«ãå¼ç¤¾ã§ã¯ Terraform ã® CI/CD ã CodeBuild ã§è¡ã£ã¦ãã¾ãã
GitHub Flow ãæ¡ç¨ãã¦ããã PR ã§ã¯ plan ãå®è¡ãã default branch ã« PR ããã¼ã¸ãããã apply ãå®è¡ãã¦ãã¾ãã
Monorepo ã«ãªã£ã¦ãããä¸ã¤ã®ãªãã¸ããªã§ 70 å以ä¸ã® state ã管çããã¦ãã¾ããã CI ã§ã¯ PR ã§å¤æ´ããããã¡ã¤ã«ã«é¢é£ãã state ã«å¯¾ãã¦ã®ã¿ plan ã apply ãå®è¡ãããããã«ãªã£ã¦ãã¾ãã
ãã® GitHub ãªãã¸ããªã§ã¯ Require branches to be up to date before merging *1ãæå¹åããã¦ãã¾ããã

ãããæå¹åããã¦ããã¨ããã PR ããã¼ã¸ããããä»ã® PR 㯠rebase ãªãã merge ãã㦠default branch ã®å¤æ´ãåãè¾¼ã¾ãªãã¨ãã¼ã¸ã§ãã¾ããã

éä¸ãã¼ã¸ã㦠CI ã®çµæãå¾ ã¤ã®ã¯é¢åãªã®ã§ããã ããããªã㨠PR ã® plan ã®çµæã¨ç°ãªãçµæã apply ãããå¯è½æ§ãããã¾ããã apply ã¯ææªåãè¿ãã®ã¤ããªãç ´å£ç夿´ãè¡ãããå¯è½æ§ããããããå¤å°å©ä¾¿æ§ãæãªã£ã¦ãå®å ¨ãªè¨å®ãããã¦ãã¾ãã *2ã
ããããæè¿ state ã®æ°ãå¢ãã¦ãã(70å以ä¸)ãã¨ã Renovate ã«ãã£ã¦é »ç¹ã« AWS provider ã update ãããããã«ãªã£ããã¨ãããã default branch ã®æ´æ°é »åº¦ãå¢ãã update branch ããªã㨠merge åºæ¥ãªãé »åº¦ãå¢ãã Developer Experience ã大ããæãªã£ã¦ãã¾ã£ã¦ãã¾ããã
ããã§å®å
¨æ§ãæ
ä¿ãã¤ã¤ Require branches to be up to date before merging ãç¡å¹åããæ¹æ³ãèãã¾ããã
解決ç
CI ã«ä»¥ä¸ã®å¤æ´ãå ¥ãã¾ããã
- PR ã® CI ã§ plan ã®å®è¡çµæã®ãã¡ã¤ã«ã S3
s3://<bucket name>/tfplan/<pr number>/<state path>/tfplan.binaryã« upload - apply ã§ã¯ upload ãã plan file ããã¦ã³ãã¼ãããããã使ã
- apply å®è¡å¾ãåã state ã«é¢ãã PR ã® CI ãå®è¡ãã plan file ãæ´æ°
Require branches to be up to date before mergingãç¡å¹å
plan ã® -out option ã§å®è¡çµæããã¡ã¤ã«ã«åºåãã S3 ã« upload ãã¾ãã S3 ã® object ã®ãã¹ã«ã¯ PR çªå·ã¨ state ãèå¥ããããã® path ãå«ãã¦ãã¾ãã default branch ã® CI ã§ã¯ plan ã¯å®è¡ããã«ã S3 ãã plan file ããã¦ã³ãã¼ããããããæå®ã㦠apply ãå®è¡ãã¾ãã PR ã® CI ã§çæããã plan file ã使ããã¨ã§ PR ã® plan ã®çµæã¨åãçµæã apply ããããããæ³å®å¤ã®ç ´å£ç夿´ã¯èµ·ãããªããªãã¾ãã
plan å®è¡æç¹ããå¾ã« state ãæ´æ°ããã¦ããã apply ã¯å¤±æãã¾ãã
$ terraform apply tfplan.binary Error: Saved plan is stale The given plan file can no longer be applied because the state was changed by another operation after the plan was created.
apply ãå®è¡ãããã state ã¯æ´æ°ãããã®ã§ãæ¢åã® PR ã® CI ãå®è¡ãã plan file ãæ´æ°ããå¿
è¦ãããã¾ãã
apply ã失æãã¦ã state ã¯æ´æ°ããããã¨ãããã®ã§å¦çãçµäºããã«ç¶è¡ãã¾ã(ãã ãæçµçã«ã¯ exit 1 ã§ CI ã失æããã¾ã)ã
ã¨ã¯ãããå¼ç¤¾ã¯ Monorepo ã«ãªã£ã¦ããã®ã§ãå®è¡ããå¿
è¦ãããã®ã¯ãåã State ã«å¯¾ãã PR ã ãã§ãã
å¼ç¤¾ã§ã¯ tfcmt ã使ã£ã¦ããã PR label ã«å¯¾è±¡ã® state ã® path ãå«ã¾ãã¦ãã¾ã(ä¾: service-1/staging/no-change)ã
ããã§ GitHub API ã§ PR ã®ãªã¹ããåå¾ãã AWS CLI ã§ PR ã® CI ãå®è¡ãã¾ãã
CodeBuild ã 㨠--source-version ã« pr/<PR çªå·> ãæå®ããã°è¯ãã®ã§ç°¡åã§ãã
get_prs() { # open 㪠PR ã 100 å以ä¸ãªãåæã§ pagination ã¯èæ ®ãã¦ããªã # shellcheck disable=SC2086 curl \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Accept: application/vnd.github.v3+json" \ "https://api.github.com/repos/<owner>/<repo>/pulls?per_page=100" | jq '.[] | select(.labels[].name | startswith("'$TARGET'/")) | .number' } while read -r pr_number; do echo "===> Start build pr/$pr_number" >&2 aws codebuild start-build \ --project-name "$PROJECT_NAME" \ --source-version "pr/$pr_number" done < <(get_prs)
CI ã rerun ããéã«ã¯ã対象㮠PR ã«ã¯æ¬¡ã®ãããªã³ã¡ã³ãã post ããããã«ãã¦ãã¾ãã

å¼ç¤¾ã® @chaspy ã® tweet ã御覧ãã ããã
Terraform ã§ PR åºãããã¨å¥ã® PR merge ã§ remote state ã®ç¶æ ãå¤ãã£ãã rerun ã㦠plan çµæåéç¥ãã¦ãããã®ãã£ã¡ã便å©ã @szkdash ã¨ããéç¥ãå®è£ ãã¦ã¾ãããå¼ç¤¾ã® Terraform ç°å¢å½å ææ°ã®ãããï¼ç¥ãããã©ï¼ãªã®ã§æ°ã«ãªãã²ã¨ã¯å ¥ç¤¾ãã¦ãã ãããhttps://t.co/SbZGJYrTbY pic.twitter.com/3vMjsXucsD
— Takeshi Kondo (@chaspy_) February 4, 2021
ãªãããã®ããæ¹ã ã¨ãåã PR ã® CI ãè¤æ°åå®è¡ãããå¯è½æ§ãããã¾ãã ä¾ãã° merge ããã PR A ã¨ãªã¼ãã³ãª PR B ã両æ¹ã¨ã service 1 㨠service 2 ã® state ã«å¯¾ãã PR ãªå ´åã§ãã default branch ã® CI ã§ service 1 ã® build 㨠service 2 ã® build ã並åã§å®è¡ããã¾ãã ãããã㨠service 1 ã® build 㨠service 2 ã® build ã®ä¸¡æ¹ã§ PR B ã® CI ãå®è¡ããããã¨ã«ãªãã¾ãã ããã¯ç¡é§ã§ã¯ããã¾ããããã»ã©å®å®³ã¯ãªãã®ã§æ°ã«ããªããã¨ã«ãã¦ãã¾ãã
CI ã§ã¯ pull/<pr number>/merge ã checkout ãããã¨ã§ default branch ã®æ´æ°ãåæ ãããç¶æ
ã§ plan ãªã©ãå®è¡ãã¾ãã
pull/<pr number>/merge 㯠GitHub ã§ PR ãä½ããããèªåã§ä½ããããªã¢ã¼ããã©ã³ãã§ãã
PR ãã³ã³ããªã¯ããã¦ããå ´åã¯ãã®ãã©ã³ãã¯ä½ãããªãã®ã§å¤±æãã¾ãããã®ã¨ãã¯ã³ã³ããªã¯ããè§£æ¶ã㦠CI ãå®è¡ãã¾ãã
git fetch --depth 1 origin "pull/$PR_NUMBER/merge:pr/$PR_NUMBER/merge" git checkout "pr/$PR_NUMBER/merge"
çµæ
å®å
¨æ§ãæ
ä¿ãã¤ã¤ Require branches to be up to date before merging ãç¡å¹åããäºãã§ãã¾ããã
å®å
¨æ§ã«é¢ãã¦ã¯ãåã« Require branches to be up to date before merging ãæå¹åãã¦ããã ãããããããå®å
¨ã«ãªãã¾ããã
ã¨ããã®ã Require branches to be up to date before merging ãæå¹ã«ããã¨ããã§ãPR ã® terraform plan ã®çµæã¨ç°ãªãçµæã apply ãããå¯è½æ§ã¯ 0 ã§ã¯ãªãããã§ãã
ã¾ããå¤ã CI ããã£ãã rerun ãã¦å¤ãè¨å®ã apply ããããã«ãªã£ã¦ãã plan file ãå¤ããã° apply ã«å¤±æããã¨ããæå³ã§ãå®å ¨ã«ãªãã¾ããã ããã«é¢ãã¦ã¯å ã default branch ã® CI ã«é¢ãã¦ã¯ææ°ã® revision ãããªã㨠CI ã失æããããã«ãããã¨ã§å¯¾å¦ãã¦ããã®ã§ãããä»åã®å¤æ´ã«ä¼´ããã®å¶ç´ã¯ãªããã¾ããã å state ã® version ã«ãã£ã¦ä¿è·ãããã¨ã§ default branch ã® CI ã失æããã¨ãã«ããæè»ã« rerun ã§ããããã«ãªãã¾ããããèªåãã¡ã§ä¿è·ããä»çµã¿ãç¬èªã«å®è£ ããªãã¦ããããªãã¾ããã
*1:GitHub ã® Branch Protection Rule ã®1ã¤
*2:ãªããæ¬çªãã¼ã¿ãã¼ã¹ã®ããã«æ¬å½ã«æ¶ããã大å¤ãªãã®ã«ã¯ Terraform ã® prevent_destroy ã AWS ã®åé¤ä¿è·è¨å®ãããã¦ãã¾ã