ArgoCD Pull Request Generatorを使ってプルリクエスト毎に環境を構築する
概要
ArgoCDを使ってプルリクエスト毎にインフラ環境を起動したのでメモ
argo-cd.readthedocs.io
前提
使用ツール
- Kubernets
- Istio
- ArgoCD
- Helm
- aqua.yamlの抜粋
- name: istio/istio/istioctl@1.17.2 - name: kubernetes/kubectl@v1.26.3 - name: argoproj/argo-cd@v2.6.7 - name: helm/helm@v3.11.2
設計
- Pull Request毎にService,Deployment etcを構築する
- Pull Request毎にRoute53レコードを作成する
- アクセス制御はIstioのVirtual Serviceを使う
- DB、キャッシュは共通のものを使う
- アプリケーションリポジトリはモノレポ構成であり、複数のアプリケーションを起動する
ワークフロー
- アプリケーションリポジトリでPull Requesetを作成する
- 変更されたアプリケーションコードを検知して自動でラベルを付与する
例:foo-api,bar-apiなど - 手動でpreviewラベルを貼るとArgoCDが検知してインフラリソースを作成する
- リソースが作成されたらPull ReqeustにURLがコメントされる
Application Setの実装イメージ
apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: preview spec: goTemplate: true generators: - pullRequest: github: owner: hogeOrg repo: fugaRepo labels: - preview requeueAfterSeconds: 1800 template: metadata: name: "preview-{{ .number }}" spec: ignoreDifferences: - group: "*" kind: ConfigMap namespace: hoge jsonPointers: - /data sources: - repoURL: "https://github.com/hogeOrg/fugaRepo.git" targetRevision: main ref: repo - repoURL: "ghcr.io/hogeOrg/fugaRepo" targetRevision: 0.0.1 chart: app-a helm: releaseName: app-a-{{ .number }} valueFiles: - $repo/clusters/dev/apps/app-a/values.yaml - secrets://https://$${GITHUB_TOKEN}@raw.githubusercontent.com/hogeOrg/fugaRepo/main/clusters/dev/apps/app-a/values.secrets.yaml parameters: - name: nameOverride value: "app-a-{{ .number }}" - name: "image.tag" value: >- {{if has "app-a" .labels -}} sha-{{ substr 0 7 .head_short_sha }} {{- else -}} main {{- end -}} - name: "image.pullPolicy" value: Always - name: "app.datadogServiceName" value: "app-a-{{ .number }}" values: | resources: requests: cpu: 500m - repoURL: "ghcr.io/hogeOrg/fugaRepo" targetRevision: 0.0.1 chart: virtual-service helm: releaseName: virtual-service-{{ .number }} parameters: - name: nameOverride value: api-virtual-service-{{ .number }} values: | annotations: external-dns.alpha.kubernetes.io/target: gateway.hoge.fuga.com external-dns.alpha.kubernetes.io/alias: "true" hosts: - api-{{ .number }}.hoge.fuga.com httpRoutes: - match: - uri: prefix: /app-a route: - destination: host: app-a-{{ .number }}.hoge.svc.cluster.local port: number: 8000 - match: - uri: prefix: / route: - destination: host: api.hoge.fuga.com port: number: 443 - chart: app-b repoURL: "ghcr.io/hogeOrg/fugaRepo" targetRevision: 0.0.1 helm: releaseName: app-b-{{ .number }} parameters: - name: "nameOverride" value: app-b-{{ .number }} - name: "image.tag" value: >- {{if has "app-b" .labels -}} sha-{{ substr 0 7 .head_short_sha }} {{- else -}} main {{- end -}} - name: "app.datadogServiceName" value: "app-b-{{ .number }}" valueFiles: - $repo/clusters/dev/apps/app-b/values.yaml - secrets://https://$${GITHUB_TOKEN}@raw.githubusercontent.com/hogeOrg/fugaRepo/main/clusters/dev/apps/app-b/values.secrets.yaml values: | resources: requests: cpu: 500m project: "hoge" destination: server: https://kubernetes.default.svc namespace: hoge syncPolicy: syncOptions: - CreateNamespace=true - RespectIgnoreDifferences=true automated: selfHeal: true prune: true
ポイント
一意なリソースを作成する
hosts: - api-{{ .number }}.hoge.fuga.com
識別子にプルリクエスト番号を入れておくことで独立した環境を構築する
イメージの動的な更新
- name: nameOverride value: "app-a-{{ .number }}" - name: "image.tag" value: >- {{if has "app-a" .labels -}} sha-{{ substr 0 7 .head_short_sha }} {{- else -}} main {{- end -}}
このように書いておくことで、
- アプリケーションコードが変更されている場合はプルリクエスト内で変更されたイメージタグ
- アプリケーションコードが変更されていない場合はmainタグ
を利用できる。
ArgoCDのApplicationSetはGo templateを利用できる。
https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/GoTemplate/
Virtual Serviceによるリクエスト制御
IstioのVirtual Serviceを使うことでインフラリソースを短い時間で作成できる。
PRを立ち上げたときに時間がかかるリソースは作らないような設計にする。(ロードバランサー、データベースなどなど)
まとめ
過去やったことをざっくりまとめた。 K8sはエコシステムが充実しているので楽。
GitHub ActionsでPull Requestのコメントを更新する
概要
GitHub Actionsでコメントを更新する方法をまとめる
サードパーティアクションはなるべく使わない
一つのコメントを更新する
gh pr commentの--edit-lastを使う
gh --version gh version 2.49.2 (2024-05-13) https://github.com/cli/cli/releases/tag/v2.49.2
https://cli.github.com/manual/gh_pr_comment
--edit-last Edit the last comment of the same author
実装例
GitHub Actionsで下記のように書けば
- 初回はコメントを書く
- すでにコメントがあったらアップデートする
という処理がかける
- name: Update comment continue-on-error: true id: update-comment run: gh pr comment ${{ github.event.pull_request.number }} -b "updated" --edit-last env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Add comment if: steps.update-comment.outcome == 'failure' run: gh pr comment ${{ github.event.pull_request.number }} -b "first comment" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
制限事項
Edit the last comment of the same author
なので、github-actions botなどで処理をすると1つのコメントしか更新できない。
matrix strategy内で実行するとコメントが上書きされる
複数のコメントを更新する
https://github.com/actions/github-script を使う
実装例
- name: add or update comment uses: actions/github-script@v7.0.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const comment = `${{ matrix.value }}` const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, }) const botComment = comments.find(comment => { return comment.user.type === 'Bot' && comment.body.includes("${{ matrix.value }}") }) if (botComment) { github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: botComment.id, body: output }) } else { github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: output }) }
これによりMatrix strategyを利用しているときもコメントを更新できる
参考
TerraformのStateやModuleに関する設計
概要
TerraformのStateやModuleに関する設計に関する考慮点をざっくり書く
名前付け
- ユビキタス言語とディレクトリ名や変数名を合わせて認知負荷を下げる
- リソース名でなく役割・機能ベースのディレクトリ名にして認知負荷を下げる
プロダクトのコードネームはユビキタス言語なのでディレクトリ名にしても問題ない - ModuleやStateのREADME.mdを書く
- https://github.com/terraform-docs/terraform-docs を使う
- うまく説明が書けない場合は設計に問題がある可能性がある
- チームメンバーが利用できるように考慮する
いつどのようなときに使えばいいかわからないものを作らない
抽象度
- 抽象度が低いModuleを作らない
例:modules/s3など
参考: https://developer.hashicorp.com/terraform/language/modules/develop#when-to-write-a-module
- 大きく作ってから小さくしていく
小さいものをまとめるほうがコストが大きいため
stateやmoduleが小さいときは上記のように抽象化を間違えて不要に小さいパターンが多いため
インフラアーキテクチャ図
アーキテクチャ図を円で囲ってstate、moduleの単位を作る
チーム
自分が把握していない変更が出るとapplyしていいかわからずコミュニケーションが発生するためチーム単位で分ける
認証情報
CI実行時などを考慮して認証情報の粒度でディレクトリを分ける
変更(デプロイ)のライフサイクル
DRYの誤用
- 不要な依存関係につながるので偶発的凝集をModuleにしない
コードの検索性
Webコンソールからリソース名やタグでコード検索したときに探しやすいように記述する
モジュール間の依存
依存関係の把握や管理が困難になるのでモジュール間の依存を作らない
特にモジュールの中で別のモジュールをローカルパス参照しない
参考: https://developer.hashicorp.com/terraform/language/modules/develop/composition
アプリケーションレイヤーとの違い
アプリケーションレイヤーと異なりライフサイクルが長く作り直しの難易度が高く、作り直しに時間がかかることを考慮する
まとめ
プレビュー環境に必要な要件を整理する
概要
プレビュー環境、マルチステージング環境などと呼ばれる環境について要件を整理する。
要件
- プルリクエスト(任意のブランチ)のコードをデプロイできる
- 他の開発者と分離された環境をデプロイできる
- プルリクエストにラベルを貼るとインフラが作成される
- プレビュー環境が必要ないプルリクエストを考慮する(Renovateなど)
- 短い時間で起動できる
- 3分以上待つのは厳しい
- 起動するリソースの種類を考慮する
- ロードバランサーやストレージは起動に時間がかかるので起動しなくて済む設計にする
- 環境変数を変更できる
- プルリクエストごとにURLが発行される
- リクエストはパスベースでルーティングする
- デプロイされたら通知が来る
- リポジトリ名
- アプリケーション名
- コミットハッシュ
- URL
- プルリクエストがクローズされたらインフラリソースが削除される
まとめ
プレビュー環境に必要な要件を整理した。
他にも検証用に開発メンバー個人専用の環境を用意する方法もある。
最近の仕事まとめ2023冬
概要
今年もお疲れ様でした。
新規プロジェクトインフラ基盤構築・レビュー
ecspresso利用
既存プロダクトではTerraformでECSをデプロイしていたが、新規プロダクトではecspressoを導入するように推進した。
https://github.com/kayac/ecspresso
Atlantis導入
AWS、Azureのマルチクラウドインフラ基盤をAtlantisで構築した。
https://www.runatlantis.io/
AWSアカウント移行
AWSのアカウント移行作業を進めた。
インフラコンポーネントとterraform moduleの粒度を揃えて認知負荷を下げた
CloudFront Distributionを下の方法で無停止で移行した https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-move
VPCの加重ルーティングでECSを移行する方針を決めた
複雑になっていたプロジェクトの進行方法を整理した
アカウント移行対象が複雑に管理されており、スケジュール調整やIAM Roleの管理周りで課題が出ていたので、リフトアンドシフトの考え方に基づいてシンプルな移行方針に変更したS3バケットをアカウント移行するときはバケット名が重複してしまうので、bucket_prefixのようなオプションを使う方針を決めた
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#bucket_prefix
ECS->EKS移行
ECSからEKS移行を進めた。
https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Generators-Pull-Request/
dev00~10のような運用をしており、動作確認をしたいときに使えないという課題を解決するため、ArgoCDのPull request generatorを使ってPRを作成したらコンテナが立ち上がるようにした。
プロジェクトは諸事情で一時中断させた。
勉強会
新メンバーに対して、Terraform勉強会とDevOps、CI/CD勉強会を開催した。
分野を増やしつつ毎年継続的にやっていきたい。
講師も交代できるレベルにしていきたい。
登壇系
SRE勉強会のLTと社内カンファレンスのLT枠で登壇した。
プロジェクトの失敗要因やSREの実践について話した。
プロジェクト管理
ロールは決まってないが無いが適切なタイミングでプロジェクト管理について、提案をしている。
チケットのテンプレートを作成したり、KPTしたり、タスクの優先順位を変更したり、DesignDoc書いたりなど。
その他
- チームや人事の課題に対して適切にエスカレーションし、他部署のメンバーを巻き込んで仕事を進めた。
- 静的解析の追加
- Renovateの更新とバージョンアップ作業
- self hosted runnerの追加
- チームメンバーの育休に備えてドキュメントの整備、育休中の対応
などコツコツやっている
まとめ
戦略面のカバーをできたのが大きかった。
来年も引き続き組織の再現性、スケーラビリティを意識して仕事していきたい。
Amazon ECRでイメージを保護する
概要
Amazon ECRでイメージを保護したいが保護のアクションがないので対応策
結論
保護したいイメージの削除期間やイメージの個数を大きな数にする
下のようなJSONにすると
- vから始まるタグ(v1.1.0など)は1年間削除されない
- sha-から始めるタグは1日で削除される
- v1.1.0とsha-xxxxxxxがついていた場合は優先順で365日削除されない
という挙動になる
{ "rules": [ { "action": { "type": "expire" }, "selection": { "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 365, "tagStatus": "tagged", "tagPrefixList": [ "v" ] }, "description": "keep released images", "rulePriority": 1 }, { "action": { "type": "expire" }, "selection": { "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 1, "tagStatus": "tagged", "tagPrefixList": [ "sha-" ] }, "description": "remove development images", "rulePriority": 2 } ] }
参考
GCPとAzureにはあった。
https://cloud.google.com/artifact-registry/docs/repositories/cleanup-policy#create
Terraformの改善活動まとめ
概要
最近のやっていたTerraformリポジトリの改善活動をまとめる
課題点
リポジトリの分割粒度
prd,stgなどの実行環境ごとにリポジトリが別れており、AWSアカウントとリポジトリが1対1でない。また、明確なルールがない環境のため
- どのリポジトリにコードを書けばいいかわからない
- どのmoduleを使えばいいかわからない(terraform-moduleリポジトリがあるのにdev-terraformリポジトリにもモジュールがある)
- リソースを書く場合リポジトリを間違える、間違いを指摘する手間が増える
- カスタムスクリプトやドキュメントが重複しており管理コストが高い
stateの粒度
1terraform resourceにつき1stateになっていたため
- apply作業を何度も実行する必要がある
- 定期的にapplyされないのでコードとリソースの差分が発生する
- インフラアーキテクチャの全体像が分かりづらい
カスタムスクリプトによる開発体験の低下
- メンテナンスできるメンバーが少ない
- 拡張性の低下
linterなどの周辺ツールを導入するためにカスタムスクリプトに大幅な変更が必要
CI変更時も同様 - terraform applyする対象をファイルで管理するためPR作成時にコンフリクトする
Terraform, AWS providerのバージョンが古い
リソースが細かいことにも相まってバージョンアップ作業が大変
やったこと
リポジトリの統合
リポジトリをmonorepo構成に変更
カスタムスクリプトの脱却
CIの拡張
CodeBuildからGitHub Actionsに変更
- Plan結果をPull Requestのコメントに表示するように変更
- apply結果をSlackに通知
- DynamoDBによるstateのlockの追加
静的解析の追加
- tfsec、tflint、conftestなどの静的解析を導入
Terraform, AWS providerのバージョンアップ
やれなかったこと
- stateの統合
- drift detection
学んだこと
やったことは基本的なことなので、そこから抽象化した学びをメモする
- 守れないルールは必要ないので、ルールと仕組み化はセットにする
- チームとして負債をハンドリングできないと返却に大幅な時間がかかる
- 負債とは正面から向き合う必要があるか考える
- 開発組織の人数規模が増えてくると基礎的なフォローアップや、同じことを繰り返し言う必要がある