前置き
GitHub Actionsが刷新されてHCLからYAMLでCIを作成できるようになりました。
前まではAction毎にDockerfileを必ず用意する必要がありましたが、Ubuntu・Windows・MacOS上でCIを回すことが可能となり、Dockerfileのメンテナンスが要らなくなりました。<- まじ感謝
では、新しくなったGitHub Actionsを使用してどのようにCIを作ったか説明していきたいと思います。
概要
今回作成してみたCIは、Pythonで開発したアプリケーションをDockerizeしてAWS ECRへプッシュするというものです。
Pushする前にソースコードのLintチェック、テストの実行、Trivyを使用した脆弱性診断、Dockleを用いたセキュリテイ診断を並列に実施します。並列に実行できるJobは20個までとなっているので、今回の規模であれば余裕で並列実行できます。
Jobを並列実行する際に注意すべきは、Job毎に仮想インスタンスが異なるのでファイルシステム上で情報が共有されないことです。情報を共有したい場合は同一のJobで全てのStepを実行する必要があります。そのため、脆弱性診断・セキュリティ診断・ECRへPushする際にそれぞれでDockerイメージをビルドしています。まぁ、トレードオフですね。
オンラインでCIを回す時に気をつけたいのが、クレデンシャルデータの扱いです。GitHubではSecretsという機能があり、データを暗号化して保存してくれます。GitHub Actionsでも、Secretsに設定されている値にアクセスすることができるので、YAMLにクレデンシャルデータを直接書かなくて済みます。
処理の流れ
ソースコード
サクッと、完成形だけ知りたい方のためにソースコードを書いておきます。
※[追記] trivyのリポジトリがaquasecurity/trivyに変更しました
on:
push:
branches:
- master
- develop
name: Build and Push Image to ECR
jobs:
lint:
name: Lint check
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@master
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
version: '3.7'
architecture: 'x64'
- name: Install dependencies
run: |
pip install --upgrade pip pipenv flake8
pipenv install --system
- name: Lint check with flake8
run: flake8 src tests
test:
name: Python Test
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@master
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
version: '3.7'
architecture: 'x64'
- name: Install dependencies
run: |
pip install --upgrade pip pipenv pytest
pipenv install --system
- name: Test with pytest
run: python -m pytest -v
trivy:
name: Trivy Scan Vulnerability
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@master
- name: Build docker image
run:
docker build -t ${IMAGE_NAME} . --file Dockerfile
env:
IMAGE_NAME: app
- name: Install trivy
run: |
sudo apt-get install apt-transport-https gnupg
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -cs) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy
- name: Vulnerability Scan with Trivy
run: trivy --only-update alpine -q --severity HIGH,CRITICAL --exit-code 1 ${IMAGE_NAME}
env:
IMAGE_NAME: app
dockle:
name: Dockle
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@master
- name: Build docker image
run: docker build -t ${IMAGE_NAME} . --file Dockerfile
env:
IMAGE_NAME: app
- name: Install dockle
run: |
VERSION=$(curl --silent "https://api.github.com/repos/goodwithtech/dockle/releases/latest" | \
grep '"tag_name":' | \
sed -E 's/.*"v([^"]+)".*/\1/' \
)
curl -L -o dockle.deb https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.deb
sudo dpkg -i dockle.deb
rm dockle.deb
- name: Check image with dockle
run: dockle ${IMAGE_NAME}
env:
IMAGE_NAME: app
build:
name: Build and Push Docker Image
runs-on: ubuntu-18.04
needs: [lint, test, trivy, dockle]
steps:
- uses: actions/checkout@master
- name: Setup Python 3.7 for awscli
uses: actions/setup-python@v1
with:
version: '3.7'
architecture: 'x64'
- name: Install awscli
run: pip install --upgrade pip awscli
- name: Login to ECR
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: $(aws ecr get-login --no-include-email --region ap-northeast-1)
- name: Build docker image
env:
CONTAINER_REGISTRY_PATH: ${{ secrets.CONTAINER_REGISTRY_PATH }}
IMAGE_NAME: app
run: |
TAG=`echo $GITHUB_REF | sed 's/refs\/heads\///g'`
docker build -t ${CONTAINER_REGISTRY_PATH}/${IMAGE_NAME}:${TAG} .
- name: Notify push start to Slack
run: curl -X POST -H 'Content-type:application/json' --data '{"text":"'"${GITHUB_REPOSITORY}"'\n【START】Push Image to ECR!!"}' ${{ secrets.SLACK_WEBHOOK }}
- name: Push image to ECR
env:
CONTAINER_REGISTRY_PATH: ${{ secrets.CONTAINER_REGISTRY_PATH }}
IMAGE_NAME: app
run: |
TAG=`echo $GITHUB_REF | sed 's/refs\/heads\///g'`
docker push ${CONTAINER_REGISTRY_PATH}/${IMAGE_NAME}:${TAG}
- name: Notify push finish to Slack
run: curl -X POST -H 'Content-type:application/json' --data '{"text":"'"${GITHUB_REPOSITORY}"'\n【FINISH】Pushed Image to ECR!!"}' ${{ secrets.SLACK_WEBHOOK }}
解説
ソースコードに書かれていることを分割して解説していきます。
イベントトリガー
on:
push:
branches:
- master
- develop
on
によってトリガーとなるイベントの設定をします。
設定できるイベントはpush、issues、pull_requestなどがあります。全てのイベントを知りたい方はEvents that trigger workflows - GitHub Helpを参照してください。
ここではpushをトリガーとして、かつmasterとdevelopブランチのみを対象としています。イベント対象を制限するためにbranches以外にtagsとpathsがあります。tagsは名前の通りですが、pathsは対象となるファイルやディレクトリを指定します。
Jobs
jobs:
lint:
name: Lint check
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@master
jobs
で実行したいCIのフローを宣言します。複数のjobがある場合はデフォルトで並列実行されます。<-すごい(小並感)
シーケンシャルにjobを実行したい場合はneeds
を使います。これによって、指定されたjobが完了するまで待ちます。
lint
はjobのIDです。jobsにおいて一意である必要がありますが、割り当てるIDはお好みでOKです。
runs-on
で仮想インスタンスを指定します。指定できるOSはUbuntu、MacOS、Windowsです。また、バージョンの指定もできます。
これでjobが実行できる環境が整ったので、stpes
で実際の処理内容を宣言します。
Steps
steps:
- uses: actions/checkout@master
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
version: '3.7'
architecture: 'x64'
- name: Install dependencies
run: |
pip install --upgrade pip pipenv flake8
pipenv install --system
- name: Lint check with flake8
run: flake8 src tests
steps
では処理内容を記述していきます。全てのsteps内の処理は同一仮想インスタンス上で実行されます。情報共有したい場合は同一steps内に処理を書きましょう。
uses
は利用したいアクションを指定します。同一リポジトリにあるアクション、パブリックリポジトリのアクション、公開されているDockerイメージを指定することができます。stepsの一番最初に宣言しているアクションactions/checkout@master
は、裏ではgit fetch
とgit checkout $GITHUB_SHA
と同様のことを実行しています。つまり、イベントトリガーとなったコミットへチェックアウトしてくれます。このアクションは必ず指定しましょう。ちなみにactions/setup-python@v1は裏ではTypeScriptが動いています。
with
はアクションの入力値を指定します。何を指定可能かはアクションごとのYAMLに書かれているので、そちらを確認してください。
実行したいコマンドはrun
で指定します。実行したいコマンドが長くなりすぎる場合は可読性が落ちますのでアクションを作成することをお勧めします。
環境変数
env:
IMAGE_NAME: app
環境変数はenv
で指定します。「environment」とフルで書く必要がないので楽です。
Secrets
${{ secrets.AWS_ACCESS_KEY_ID }}
Secretsに設定した値にアクセスしたい場合は、上記のように書きます。secretsの後に取得したい値のキー(AWS_ACCESS_KEY_ID)を指定します。
余談
余談ですが、GitHub Actionsの実行画面がかなりリッチになりました。
おわりに
GitHub ActionsでCIを書いてみましたが、他のCIツールと遜色がないぐらいのクオリティになったと感じました。ただ、CIの結果を通知する手段がメールしかサポートされていないのは痛いです。今回のCIでは、とりあえずcurlでSlackへ通知する処理を組み込みましたが、アクションを作った方がいいと思いました。
本記事が、これからGitHub ActionsでCIを書こうと思っている方の参考になれば幸いです