こんにちは、Red HatでOpenShift関連のプリセールスをしている北村です。
前回の記事ではGolden Path実装の前段として、SonarQubeの導入やDeveloper Hubとの連携、そしてAWS ECRへの認証設定などを行いました。
この記事ではいよいよGolden Pathの中身について解説していきます。
- 【第1回】Developer Hubをインストールしてみよう
- 【第2回】GitHubを使用した認証を実装しよう
- 【第3回】GitLabを使用した認証を実装しよう
- 【第4回】Componentを作成・登録しよう
- 【第5回】Software Templateを使ってアプリのデプロイをセルフサービス化しよう
- 【第6回】TechDocsを作成・登録しよう
- 【第7回】Golden Pathの実装をマスターしよう - 前編
- 【第8回】Golden Pathの実装をマスターしよう - 後編 ←この記事
今回実装する環境(おさらい)
今回は以下のような環境を構築していきます。
- 利用者はDeveloper Hubの画面で必要なパラメータを入力してTemplateを実行する
- Skeleton Repoからソースコードを取得し、ユーザーが入力したパラメータを代入する
- 新しいGitHubリポジトリ(app用リポジトリとmanifest用リポジトリ)にPushする
- Hello worldアプリ用のK8sマニフェストをデプロイするためのApplication CRを作成する
- リポジトリの作成を契機にアプリをビルド→デプロイするGitHub Actionsを実行する
- ビルドされたコンテナイメージはAWS ECRに保管する
- Application CRが新しいGitHubリポジトリ上のK8sマニフェストを検知してアプリをデプロイする
Golden Path実行の準備
今回実行するGolden Pathはこちらのリポジトリを利用します。このリポジトリを自分の環境にForkしてきてください。
Golden Pathの登録
ForkしてきたGolden Pathリポジトリにあるtemplate.yamlをDeveloper Hubに登録していきます。
app-config-rhdh.yaml
を修正して、このファイルを常に読み込む状態にします。
app-config-rhdh.yaml
kind: ConfigMap apiVersion: v1 metadata: name: app-config-rhdh namespace: rhdh annotations: rhdh.redhat.com/backstage-name: developer-hub data: app-config-rhdh.yaml: | app: ...omit... backend: ...omit... auth: ...omit... integrations: ...omit... signInPage: github catalog: ...omit... locations: - type: url target: https://github.com/<GitHub Organization名>/helloworld-skeleton/blob/main/template.yaml schedule: frequency: PT60S initialDelay: PT30S timeout: PT120S # ここから下を追記 - type: url target: https://github.com/<GitHub Organization名>/helloworld-goldenpath/blob/main/template.yaml # 先ほどForkしたリポジトリのtempalte.yamlを指定 schedule: frequency: PT60S initialDelay: PT30S timeout: PT120S
この設定を反映します。
oc apply -f app-config-rhdh.yaml
Podが再起動するのでアクセスすると、Templateが1つ追加されていることがわかります。このHello world app with CICDが今回のGolden Pathになります。
まずは実行してみよう
中身の説明は後回しにして、一回このGolden Pathを実行してみましょう。
実際に選択して必要項目を入力していきますが、入力する内容は第5回で作成したものと全く一緒のものになります。
実際にデプロイ後、コンポーネント画面に移動すると、これまでのものとはいくつか異なる点があるかと思います。
- LinkにApp RepositoryとManifest Repositoryの2つが存在する
- CIというタブが増えている
- Sonarqubeのスキャン結果を確認できるカードが増えている
Linkを選択すると、ソースコードを管理する<repo-name>-app
とK8sマニフェストを管理する<repo-name>-manifest
の2種類のリポジトリに遷移できます。これらはGolden Pathの実行によって自動生成されました。
次にCIタブに移動すると、2つのパイプラインが実行されていることが確認できます。ひとつ目がコンテナをビルド・デプロイするためのCICDパイプラインworkflowです。ふたつ目が第6回で作成したTechdocsを作成・登録するためのworkflowです。
Messageの部分のリンクを選択すると、該当のworkflowの詳細画面が表示されます。LinksにあるWorkflow runs on GitHubを選択すると、GitHubの画面にも遷移できます。
実際に遷移してみましょう。すると4つのjobが実行されています。
- sonar_scan : ソースコードに対してSonarqubeによるスキャンを実行します。
- build_and_push : コンテナをビルドしてECRにPushします。
- update_manifests : manifestリポジトリ側のk8sマニフェスト内のイメージタグをビルドしたイメージのものに書き換えます。
- deploy_application : ArgoCDのSyncを実行してアプリケーションを更新します。
つまり、開発者がこのGolden Pathを実行しただけで、自分たち専用のGitリポジトリが払い出され、その中にサンプルコードが格納されており、かつCIパイプラインが実行され、静的解析が走り、コンテナがビルド・デプロイされることになります。さらにその周辺のツールへもこのコンポーネント画面から簡単にアクセスできるようになりました。便利でしょう?
Golden Pathの中身を確認してみよう
ではこのGolden Pathがどのように実装されているか、中身を見てみましょう。
$ tree -L 1 . ├── README.md ├── app-skeleton ├── manifest-skeleton └── template.yaml
ディレクトリ直下を見てみると、app-skeleton
とmanifest-skeleton
、そしてtemplate.yaml
が存在します。このskeletonリポジトリがGolden Pathを実行し、リポジトリを払い出すときの元となります。
template.yaml
まずはじめにtemplate.yaml
の中身を上から順に確認していきます。なお、パラメータ入力の部分は前回のものと同様のため割愛し、Stepの部分から見ていきます。
Fetch App skeleton
steps: - id: fetch-app name: Fetch App skeleton action: fetch:template input: url: ./app-skeleton values: app_name: ${{ parameters.app_name }} owner: ${{ parameters.owner }} git_repo_name: ${{ parameters.git_repo_name }} git_host_url: ${{ parameters.git_host_url }} git_owner_name: ${{ parameters.git_owner_name }} targetPath: ./app-tenant - id: fetch-manifest name: Fetch Manifest skeleton action: fetch:template input: url: ./manifest-skeleton values: app_name: ${{ parameters.app_name }} owner: ${{ parameters.owner }} git_repo_name: ${{ parameters.git_repo_name }} git_host_url: ${{ parameters.git_host_url }} git_owner_name: ${{ parameters.git_owner_name }} targetPath: ./manifest-tenant
ここは前回のTemplateでも行っていたfetch:template
というactionです。前回ではmanifest-skeleton
だけでしたが、今回は開発者が実際にコーディングを行うアプリ用のリポジトリを生成すべく、app-skeleton
からもfetchを行っています。
Publish GitHub
- id: publish-app name: Push App Repo to GitHub action: publish:github input: repoUrl: ${{ parameters.git_host_url }}?owner=${{ parameters.git_owner_name }}&repo=${{ parameters.git_repo_name }}-app repoVisibility: public sourcePath: ./app-tenant defaultBranch: develop protectDefaultBranch: false - id: publish-manifest name: Push Manifest Repo to GitHub action: publish:github input: repoUrl: ${{ parameters.git_host_url }}?owner=${{ parameters.git_owner_name }}&repo=${{ parameters.git_repo_name }}-manifest repoVisibility: public sourcePath: ./manifest-tenant defaultBranch: develop protectDefaultBranch: false
ここでGitHubにPushを行います。repoUrl
の末尾にそれぞれ-app
と-manifest
がついており、この命名規則に従ってリポジトリが生成されます。
ArgoCD Deploy
- id: argocd name: Deploy with ArgoCD action: argocd:create-resources input: appName: ${{ parameters.app_name }}-init argoInstance: main namespace: openshift-gitops repoUrl: https://${{ parameters.git_host_url }}/${{ parameters.git_owner_name }}/${{ parameters.git_repo_name }}-manifest.git path: 'argocd/'
ここは前回とほぼ同じく、ArgoCDのapplication
リソースをデプロイするActionです。repoUrl
だけmanifestリポジトリをちゃんと参照するように変更されています。
Register Component
- id: register name: Register Catalog into Developer Hub action: catalog:register input: repoContentsUrl: ${{ steps['publish-app'].output.repoContentsUrl }} catalogInfoPath: "/catalog-info.yaml"
ここも前回同様、新しく生成されたcatalog-info.yaml
をDeveloper Hubに登録しているActionです。今回のcatalog-info.yaml
はappリポジトリ側に存在するため、repoContentsUrl
の部分だけstepの参照先がpublish-app
に変わっています。
Outputs
output: links: - title: Componentを開く icon: catalog entityRef: ${{ steps['register'].output.entityRef }} - title: Application Gitリポジトリを開く url: ${{ steps['publish-app'].output.remoteUrl }} icon: github - title: Manifest Gitリポジトリを開く url: ${{ steps['publish-manifest'].output.remoteUrl }} icon: github
最後にoutputの部分ですが、ここではGolden Pathの完了後にComponent、Application Gitリポジトリ、Manifest Gitリポジトリに遷移できるリンクを生成しています。
以上がtemplate.yaml
の全貌です。意外と前回までのTemplateと大差がないことがわかったかと思います。実はこのSofeware Template機能で実行すること事態はそれほどバリエーションが多くなく、結局はいかにSkeletonリポジトリを作り込むかが現時点のDeveloper Hubにおける重要なポイントになります。ただこれから様々なActionが追加され、このSoftware Templateからもさらにいろんな作業ができるようになっていきます(多分)ので、Developer Hubの進化を楽しみしてください。
Manifestリポジトリ
次にManifest用のリポジトリを見ていきましょう。こちらも多少ディレクトリ構成が変わっていますが、大きな変化はありません。
$ tree manifest-skeleton manifest-skeleton ├── argocd │ └── application.yaml └── kustomize ├── base │ ├── app-sa.yaml │ ├── deployment.yaml │ ├── ecr-secret.yaml │ ├── kustomization.yaml │ ├── route.yaml │ └── service.yaml └── overlays └── dev └── kustomization.yaml
マニフェストを格納するディレクトリを名をkustomize
とし、その下にbase
とoverlays
の階層を設けています。今回の環境ではdevelop
ブランチのみなので、overlays
配下にはdev
のみを切っています。
前回と大きく変わった点としては、overlays/dev
配下にあるkustomization.yaml
です。
kustomize/overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base images: - name: ___IMAGE_URL___@___IMAGE_DIGEST___
このimages
の部分にname: ___IMAGE_URL___@___IMAGE_DIGEST___
と記載しています。これはのちほどCIパイプラインの中で新しいイメージタグに書き換えるためにあります。
このために、deployment.yaml
にも同様のimage名が設定されています。
deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: ...omit... spec: ...omit... template: ...omit... spec: containers: - image: ___IMAGE_URL___@___IMAGE_DIGEST___ ...omit...
また、base
配下にECR Secret OperatorのSecret
カスタムリソースをデプロイするecr-secret.yaml
と、サービスアカウントをデプロイするためのapp-sa.yaml
が追加されています。
ecr-secret.yaml
apiVersion: ecr.mobb.redhat.com/v1alpha1 kind: Secret metadata: name: ecr-secret spec: generated_secret_name: ecr-auto-generated-secret ecr_registry: ___AWS_ACCOUNT_ID___.dkr.ecr.___AWS_REGION___.amazonaws.com frequency: 10h region: ___AWS_REGION___
specにECRの情報や名前を記入することで、ECRの認証情報を持つSecretを自動生成します。
___AWS_ACCOUNT_ID___
や___AWS_REGION___
という記載がありますが、こちらはGitHub Actions内で実際の値に置換されるのでこのままでOKです。
app-sa.yaml
--- apiVersion: v1 kind: ServiceAccount metadata: name: ${{ values.app_name }}-sa imagePullSecrets: - name: ecr-auto-generated-secret --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: ${{ values.app_name }}-sa-admin subjects: - kind: ServiceAccount name: ${{ values.app_name }}-sa namespace: ${{ values.app_name }} roleRef: kind: ClusterRole name: admin apiGroup: rbac.authorization.k8s.io
こちらはアプリケーションのデプロイ、すなわち該当イメージのPullに使用されるサービスアカウントです。imagePullSecrets
に自動生成されるECR認証情報のSecret名を記入しておくことで、その認証情報を使ってECRからイメージをPullしてきます。
以上がManifestリポジトリの主な変更点です。
Appリポジトリ
次にAppリポジトリについてです。
$ tree app-skeleton -L 1 -a app-skeleton ├── .dockerignore ├── .github ├── .gitignore ├── Dockerfile ├── README.md ├── catalog-info.yaml ├── docs ├── mkdocs.yml ├── package-lock.json ├── package.json └── src
今回は簡単なHello Worldアプリのため、ディレクトリ構成もシンプルです。Techdocs生成に必要なdocs
やmkdocs.yml
、そしてコンポーネント画面用のcatalog-info.yaml
はこちらのリポジトリに移してあります。
このディレクトリで重要なのはやはりGitHub Action用のworkflowファイルです。今回は新たにビルド・デプロイ用のパイプラインファイルbuild-deploy.yaml
が追加されています。早速中身を見てみましょう。
.github/workflows/build-deploy.yaml
SonarQube Scan
jobs: sonar_scan: runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' steps: - name: Checkout code uses: actions/checkout@v3 with: fetch-depth: 0 - name: SonarQube Scan uses: sonarsource/sonarqube-scan-action@master env: SONAR_TOKEN: ${{ "${{" }} secrets.SONARQUBE_USER_TOKEN }} SONAR_HOST_URL: ${{ "${{" }} vars.SONARQUBE_URL }} with: args: > -Dsonar.projectKey="${{ values.app_name }}"
まず最初のJobはSonarqubeによるスキャンです。sonarsource/sonarqube-scan-action@master
アクションを使って実行します。必要な変数はGitHub Organization Secrets/Variablesから取ってきたり、Developer HubのGolden Path Tempalte実行時のユーザー入力値から持ってきます
GitHubから取得する場合の記載方法が${{ "${{" }} secrets.SONARQUBE_USER_TOKEN }}
と不思議な記載になっていますが、これはScaffolderのfetch:template
アクションでの変更を防ぐためになります。
奇しくもScaffolderのfetch:template
アクションとGitHubの変数の代入ロジックがともに${{...}}
となっており、これをそのまま${{ secrets.SONARQUBE_USER_TOKEN }}
と記述してしまうとScaffolderのfetch:template
アクション側で代入が走り、結果としてnull値となってしまいます。今回のように${{ "${{" }} secrets.変数名 }}
とすることで、fetch:template
で${{ "${{" }}
→${{
という変換をしてもらいます。
Build and Push
build_and_push: runs-on: ubuntu-latest needs: [sonar_scan] permissions: id-token: write contents: read steps: - name: Checkout source code uses: actions/checkout@v3 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v3 with: role-to-assume: arn:aws:iam::${{ "${{" }} secrets.AWS_ACCOUNT_ID }}:role/GitHubActionsPushECRRole role-session-name: GitHubActionsSession aws-region: ${{ "${{" }} secrets.AWS_REGION }} - name: Login to Amazon ECR id: ecr-login uses: aws-actions/amazon-ecr-login@v1 with: mask-password: 'true' - name: Create ECR repository if not exists run: | REPOSITORY_NAME="${{ values.app_name }}" if ! aws ecr describe-repositories --repository-names "$REPOSITORY_NAME" > /dev/null 2>&1; then aws ecr create-repository --repository-name "$REPOSITORY_NAME" fi - name: Build Docker image run: | docker build -t ${{ "${{" }} steps.ecr-login.outputs.registry }}/${{ values.app_name }}:${{ "${{" }} github.sha }} . - name: Push Docker image run: | docker push ${{ "${{" }} steps.ecr-login.outputs.registry }}/${{ values.app_name }}:${{ "${{" }} github.sha }}
次に行っているのがコンテナのビルドとECRへのイメージのPush処理になります。
- Configure AWS Credentials : 第7回で設定したGitHub Actions用のIAMロールを指定し、認証情報を取得しています。
- Login to Amazon ECR : ECRへログインします。
- Create ECR repository if not exists : 該当のECRリポジトリが存在しない場合にECRの作成処理を行います。
- Build Docker image : コンテナイメージをビルドします
- Push Docker image : ECRへイメージをPushします。
Update Manifests
update_manifests: runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' needs: [build_and_push] steps: - name: Create token for manifest repository id: app-token uses: actions/create-github-app-token@v1 with: app-id: ${{ "${{" }} secrets.GITHUBAPP_ID }} private-key: ${{ "${{" }} secrets.GITHUBAPP_PRIVATE_KEY }} owner: ${{ values.git_owner_name }} repositories: ${{ values.git_repo_name }}-manifest - name: Checkout manifest repository uses: actions/checkout@v4 with: repository: ${{ values.git_owner_name }}/${{ values.git_repo_name }}-manifest path: ${{ values.git_repo_name }}-manifest token: ${{ "${{" }} steps.app-token.outputs.token }} - name: Install kustomize run: | curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash sudo mv kustomize /usr/local/bin - name: Update Manifests run: | cd ${{ values.git_repo_name }}-manifest/kustomize/overlays/dev kustomize edit set image ___IMAGE_URL___@___IMAGE_DIGEST___=${{ "${{" }} secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ "${{" }} secrets.AWS_REGION }}.amazonaws.com/${{ values.app_name }}:${{ "${{" }} github.sha }} sed -i "s|___AWS_ACCOUNT_ID___|${{ "${{" }} secrets.AWS_ACCOUNT_ID }}|g" ../../base/ecr-secret.yaml sed -i "s|___AWS_REGION___|${{ "${{" }} secrets.AWS_REGION }}|g" ../../base/ecr-secret.yaml - name: Commit and push changes run: | cd ${{ values.git_repo_name }}-manifest git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add . git diff-index --quiet HEAD || git commit -m "Update kustomize manifests for commit ${{ github.sha }}" git push origin HEAD:develop
次に行っているupdate_manifests
Jobでは、パイプライン上でmanifest
リポジトリをチェックアウトしてきて、今回ビルドしたコンテナイメージの情報をもとにマニフェストを書き換える処理を行っています。
- Create token for manifest repository : GitHub Appの認証情報を利用して、対象となるManifestリポジトリにアクセスするためのアクセストークンを生成します。
- Checkout manifest repository : 前のステップで生成したトークンを利用して、対象のManifestリポジトリをチェックアウトします。
- Install kustomize : 次ステップで利用するkustomizeコマンドをインストールします。
- Update Manifests :
kustomize edit set image
コマンドで、プレースホルダーになっている___IMAGE_URL___@___IMAGE_DIGEST___
を実際のECRのURLとgithub.sha
で表されるコミット識別子に更新しています。また、sed コマンドでbase/ecr-secret.yaml
内の AWS アカウントIDおよびリージョンのプレースホルダーを実際の値に置換します。 - Commit and push changes : 変更内容をコミットし、develop ブランチにプッシュします。
Deploy Application
deploy_application: runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' needs: [update_manifests] steps: - name: Install ArgoCD CLI run: | curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64 chmod +x argocd sudo mv argocd /usr/local/bin/argocd - name: Sync and wait for deployment run: | argocd login "${{ "${{" }} vars.ARGOCD_INSTANCE_URL }}" --username "${{ "${{" }} secrets.ARGOCD_USERNAME }}" --password "${{ "${{" }} secrets.ARGOCD_PASSWORD }}" --skip-test-tls --grpc-web argocd app sync ${{ values.app_name }} --revision develop argocd app wait ${{ values.app_name }} --health
最後のJobはArgoCDにSyncコマンドを送信するためのものです。これにより、前のJobで変更したマニフェストの内容を実環境に反映させます。
- Install ArgoCD CLI : ArgoCD CLIをインストールします。
- Sync and wait for deployment : ROSA上のArgoCDにログインして、syncを実行します。
以上が今回のCICDパイプラインの全貌です。
おわりに
前回と今回の記事で、簡単ですが実践的なGolden Pathの準備と実行を実現してきました。
これはコンテナ環境におけるCICDのベースとなるものになります。これから様々なJob(例えばイメージのスキャンやSBOMの作成など)を追加して、自分なりのGolden Pathにブラッシュアップしていっていただけばと思います。