「GitHub CI/CD実践ガイド――持続可能なソフトウェア開発を支えるGitHub Actionsの設計と運用」でのメモや手を動かした記録
元リポジトリ:https://github.com/tmknom/example-github-cicd/
code:bash
$ gh repo create my-github-actions-handson --public --clone --add-readme
https://github.com/tossyi/my-github-actions-handson をpublicリポジトリで作成
Actionsのログを確認する
ワークフローが終了している場合は、「gh run view」で確認できる
code: bash
$ gh run view
? Select a workflow run ✓ add: 2-1 file, Hello 2-1 2m3s ago
✓ 2-1 Hello ·
Triggered via push about 2 minutes ago
JOBS
✓ hello in 3s (ID xxx)
For more information about the job, try: gh run view --job=xxx
詳細を確認するには、「gh run view --job=xxx」、fullでログを見る場合は「gh run view --log --job=xxx」
workflow_dispatchで手動でワークフローを実行する
code: yml
name: Manual
on:
workflow_dispatch:
inputs:
greeting:
type: string
default: Hello
required: true
description: A cheerful word
ブラウザでの実行の場合、Actionsで該当のワークフローを選択する。inputsで指定したgreetingに入力したstringが入る。
https://scrapbox.io/files/68a97e4eaeae39da399270a5.png
CLIからの実行は以下。
code: bash
$ gh workflow run manual.yml -f greeting=goodbye
✓ Created workflow_dispatch event for manual.yml at master
To see runs for this workflow, try: gh run list --workflow="manual.yml"
ジョブの実行環境
GitHub Actionsのランナーは2種類に大別できる
- GitHub-Hosted Runners
- Self-Hosted Runners
https://github.com/marketplace でアクションを探せる
コンテキスト
コンテキストは実行時の情報や、ジョブの実行結果などを保持するオブジェクト
code: text
# コンテキストの一覧
github job runner matrix env jobs secrets needs vars steps strategy inputs
コンテキストから値を取り出すには、${{ github.actor }} のように記述
環境変数
環境変数の参照は、${BRANCH} のように書くことで参照できる
以下は、コンテキストを直接シェルコマンドで呼び出している。アンチパターン。これはコンテキストによって特殊文字が含まれ、シェルコマンドの実行に意図しない影響を与える恐れがある。
code: yml
name: Contexts
on: push
jobs:
print:
runs-on: ubuntu-latest
steps:
- run: echo "${{ github.actor }}"
これは中間環境変数で回避できる。
code: yml
name: Intermediate environment variables
on: push
jobs:
print:
runs-on: ubuntu-latest
env:
ACTOR: ${{ github.actor }} # コンテキストの値を環境変数へセット
steps:
- run: echo "${ACTOR}" # 環境変数経由でコンテキストのプロパティを参照
おすすめのコーディングルール
コンテキストはシェルコマンドへハードコードせず、環境変数を経由して渡す(意図しないコンテキストが来ても大丈夫)
環境変数は全てダブルクォーテーションで囲む(シェルの仕様として、トークン分割やパス名展開が抑止される)
Variables
Variablesの登録。リポジトリ単位で登録可能。Variablesの参照は、${{ vars.USERNAME }} のようにvarsコンテキストからアクセス。
code: bash
$ gh variable set USERNAME --body 'octocat'
✓ Created variable USERNAME for tossyi/my-github-actions-handson
https://scrapbox.io/files/68a9afc26b45af2b0234624c.png
code: yml
name: Variables
on: push
jobs:
print:
runs-on: ubuntu-latest
env:
USERNAME: ${{ vars.USERNAME}}
steps:
- run: echo "$USERNAME"
Secrets
機密情報はSecretsで扱う。リポジトリ単位で登録可能。Secretsの参照は、${{ secrets.PASSWORD }}のようにsecretsコンテキストからアクセス。Secretsはログ出力しないこと。
code: bash
$ gh secret set PASSWORD --body 'xxxxx'
✓ Set Actions secret PASSWORD for tossyi/my-github-actions-handson
プルリクエスト作成時にコメントする
code: yml
name: Comment
on: pull_request
jobs:
comment:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- run: gh pr comment "${GITHUB_HEAD_REF}" --body "Hello, ${GITHUB_ACTOR}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
gh pr commentのGitHub APIの実行により、プルリクエストにコメントを書き込む
GitHub APIの実行をするためには、認証が必要
GitHub Actionsでは、簡単に使えるクレデンシャルとして「GITHUB_TOKEN」がある
GITHUB_TOKENはsecretsコンテキストから取得可能。もしくは、${{ github.token }}でも取得できる。なお、事前登録は不要
GitHub Actionsでは環境変数を経由して、GitHub APIにクレデンシャルを設定する
GITHUB_TOKENの権限はパーミッション(permissions)で制御する
matrix
matrixは一つのジョブ定義で、複数のジョブを実行できる
code: yml
jobs:
print:
strategy:
matrix:
os: ubuntu-latest, windows-latest, macos-latest
runs-on: ${{ matrix.os }}
steps:
- run: echo "${RUNNER_OS}"
shell: bash
GitHub Release
リリースノートの作成
code: bash
$ date > example.txt
$ gh release create v0.1.0 --title "v0.1.0" --notes "Wonderful Text"
https://github.com/tossyi/my-github-actions-handson/releases/tag/v0.1.0
https://scrapbox.io/files/68aa5411a0d057745b363473.png
リリースノートの自動作成
.github/workflows/relaese.yml に記載する。
リリースの自動化
code: yml
name: Release
on:
push:
tags:
- 'v0-9+.0-9+.0-9+'
jobs:
release:
runs-on: ubuntu-latest
env:
VERSION: ${{ github.ref_name }}
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/set-up@v5
with:
go-version: '1.22'
- run: |
go build -ldflags "-X main.version=${VERSION}" \
-o "${{ runner.temp }}/example" go/example/main.go
- run: |
gh release create "${VERSION}" --title "${VERSION}" --generate-notes
gh release upload "${VERSION}" "${{ runner.temp }}/example"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ghコマンドでのプルリクエスト作成とマージ
code: bash
$ gh pr create --fill-first --label "enhancement" #ラベルを付与し、first commitをタイトルとbodyに利用し、プルリクエストを作成
--fill-first
Use first commit info for title and body
https://cli.github.com/manual/gh_pr_create
code: bash
$ gh pr merge --merge
-m, --merge
Merge the commits with the base branch
https://cli.github.com/manual/gh_pr_merge
code: bash
$ git tag v0.3.0
$ git push origin v0.3.0
コンテナイメージビルドとContainer Registryへのプッシュ
docker/example/Dockerfile
code: Dockerfile
FROM nginx:1.25-alpine
RUN echo 'Hello GitHub Packages' > /usr/share/nginx/html/index.html
code: bash
$ export GHCR_USER=$(gh config get -h github.com user)
$ docker build -t ghcr.io/${GHCR_USER}/example:latest docker/example
Container Registryへのアクセスには、GitHubのクレデンシャルが必要。GitHub CLIのクレデンシャルは、GitHub Packagesへのアクセスがデフォルトでは拒否されるので、次のように権限追加する
code: bash
$ gh auth refresh --scopes write:packages
Container Registryへログイン
code: bash
$ gh auth token | docker login ghcr.io -u ${GHCR_USER} --password-stdin
Login Succeeded
Container Registryへイメージをプッシュ
code: bash
$ docker push ghcr.io/${GHCR_USER}/example:latest
https://github.com/<OWNER>?tab=packages
https://scrapbox.io/files/68aaa49f3c982f238672cf9a.png
コンテナイメージの自動リリース
code: yml .github/workflows/publish.yml
name: Publish
on:
workflow_dispatch:
inputs:
version:
type: string
required: true
description: 'Version to publish(e.g. 1.2.3)'
env:
IMAGE_NAME: gha-image
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}},value=v${{ inputs.version }}
type=raw,value=latest
- uses: docker/build-push-action@v5
with:
push: true
context: docker/example
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
code: bash
$ gh workflow run publish.yml -f version=0.1.0
「gha-image」という名前のコンテナイメージがGitHub Packagesにプッシュされる。
--------------------------------------------------
OpenID Connectのハンズオン
第11章の作業のリポジトリはリスクが高い作業を実施するため、プライベートリポジトリで実施する
コマンド:https://github.com/tmknom/example-github-cicd/tree/main/command/11
code: bash
$ gh repo create gh-oidc --private --clone --add-readme
AWS CloudShellへアクセス
code: AWS CloudShell
$ aws --version
# AWSアカウントIDの取得
$ aws sts get-caller-identity --query Account --output text
AWS CloudShell上でAWS CLIを用いて、次のリソースを作成する
OpenID Connect Provider:OIDC Trustに該当するリソース(AWSがGitHub OIDC Providerを信頼するように設定、IDトークンと一時クレデンシャルの交換のために必須)
IAMロール:Cloud Rolesに該当するリソース
OpenID Connect Providerのリソース作成
code: AWS CloudShell
$ aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 1234567890123456789012345678901234567890
{
"OpenIDConnectProviderArn": "arn:aws:iam::xxxxx:oidc-provider/token.actions.githubusercontent.com"
}
OpenIDConnectProviderArn: AWSのARNで、作成されたOIDCプロバイダーへの参照
token.actions.githubusercontent.com:GitHub ActionsのOIDCエンドポイントのパブリックURL
IAMロール
code: AWS CloudShell
$ export GITHUB_REPOSITORY=<OWNER>/<REPO>
$ export PROVIDER_URL=token.actions.githubusercontent.com
$ export AWS_ID=$(aws sts get-caller-identity --query Account --output text)
$ export ROLE_NAME=github-actions
Assume Roleポリシー(アクセス元を規定するコンポーネント)を定義
Condition定義のStringLikeに自分のリポジトリ以外も指定すると、そのowner/repoからもアクセス可能となってしまうため、注意!
https://github.com/tmknom/example-github-cicd/tree/main/command/11#assume-roleポリシーを定義したjsonファイルの作成
code: AWS CloudShell
cat <<EOF > assume_role_policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRoleWithWebIdentity",
"Principal": {
"Federated": "arn:aws:iam::${AWS_ID}:oidc-provider/${PROVIDER_URL}"
},
"Condition": {
"StringLike": {
"${PROVIDER_URL}:sub": "repo:${GITHUB_REPOSITORY}:*"
}
}
}
]
}
EOF
IAMロールの作成
code: AWS CloudShell
$ aws iam create-role \
--role-name $ROLE_NAME \
--assume-role-policy-document file://assume_role_policy.json
IAMポリシーのアタッチ
IAMポリシーであるIAMReadOnlyAccessポリシーをIAMロールへアタッチする
code: AWS CloudShell
aws iam attach-role-policy \
--role-name $ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/IAMReadOnlyAccess
ここまでで、AWS CloudShellでの作業は完了。以降は、ローカル環境での作業となる。
OpenID ConnectによるAWS連携
認証パラメータをSecretsへ登録
code: bash
$ gh secret set AWS_ID --body "<AWSアカウントID>"
$ gh secret set ROLE_NAME --body "<IAMロール名>"
AWS連携ワークフローの作成
code: yml
name: OpenID Connect
on: push
env:
ROLE_ARN: arn:aws:iam::${{ secrets.AWS_ID}}:role/${{ secrets.ROLE_NAME }}
SESSION_NAME: gh-oidc-${{ github.run_id }}-${{ github.run_attempt }}
jobs:
connect:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.ROLE_ARN }}
role-session-name: ${{ env.SESSION_NAME }}
aws-region: us-east-1
- run: aws iam list-users
- run: aws iam create-user --user-name invalid || true
ここまで、OpenID Connectのハンズオン
--------------------------------------------------
ECS、ECRへのデプロイ
https://github.com/tmknom/example-github-cicd/tree/main/command/12
AWS Copilotは、ECSやECRを構築できるコマンドラインツール。
AWS copilot CLIでは、rootユーザでなくIAMユーザを使用する。
https://aws.github.io/copilot-cli/ja/docs/credentials/
rootユーザでAWSマネジメントコンソールにログインし、IAMで該当ユーザを選択。
該当ユーザに許可ポリシーとして「AWSCloudShellFullAccess」を付与。
code: AWS CloudShell
$ aws configure
AWS Access Key ID None: <アクセスキーID>
AWS Secret Access Key None: <シークレットアクセスキー>
Default region name None: <使用するリージョン>
Default output format None: json
$ aws sts get-caller-identity
code: AWS CloudShell
copilot --version
export APP_NAME=demo
export SVC_NAME=example
export ENV_NAME=test
マニフェストファイルの作成
code: AWS CloudShell
copilot app init $APP_NAME
copilot svc init --name $SVC_NAME --app $APP_NAME \
--image nginx --port 80 --svc-type "Load Balanced Web Service"
「copilot app init $APP_NAME」実行時に、「get application demo: get application demo: AccessDeniedException: User: arn:aws:iam::xxx:user/xxx is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:xxx:parameter/copilot/applications/demo because no identity-based policy allows the ssm:GetParameter action」のエラーに対して「AmazonSSMReadOnlyAccess」を付与。
さらに、CloudFormationのエラーが出たため、「AWSCloudFormationReadOnlyAccess」、「AWSCloudFormationFullAccess」、他にも「IAMFullAccess」、「AmazonSSMFullAccess」を付与したら突破。
テスト環境の構築
code: AWS CloudShell
copilot env init --name $ENV_NAME --app $APP_NAME \
--profile default --default-config
copilot env deploy --name $ENV_NAME
copilot svc deploy --name $SVC_NAME --env $ENV_NAME
curlによる動作確認
code: AWS CloudShell
curl -I http://demo-xxx.ap-northeast-1.elb.amazonaws.com
#GitHub_CI/CD実践ガイド――持続可能なソフトウェア開発を支えるGitHub_Actionsの設計と運用_(エンジニア選書)