【DevOps】Heroku / GitHub Actionsを連携して、CI / CDパイプラインを構築する
概要
今回は「GitHub Actionsを用いた、HerokuへのCI/CDパイプライン」を構築した。まずは、詳細に作業内容を説明して、その後ハンズオンを行い、理解を深める。
作業内容
全体の流れ
今回は、CI/CDパイプラインを活用することで、新しいバージョンのソフトウェアを実行するためのステップを構築した。開発エンジニアは、GitHubレポジトリに対して、新しいコードをpushしマージする。一度新しいコードがGitHubレポジトリにマージされると、GitHub ActionsによるCI/CDパイプラインが起動して、新しいコードはHerokuサーバにデプロイされる。
具体的には、下記の図で示す通り。
- エンジニアが、新しいバージョンのコードを実装したら、GitHubへpushしマージする。
- コードがmainブランチでマージされると、それがトリガーとなってGitHub Actionsが実行される。
- GitHub Actionsでは、事前に登録した自動テストを行い、テストが成功されるとHerokuに対してデプロイする。
- デプロイが完了されると、HerokuのURLにてアプリの新しいバージョンが反映される。
言葉の説明
概要理解が難しいと思うので、各単語について説明する。
CI/CDパイプライン
CI/CDパイプラインとは「継続的インテグレーション(CI)」と「継続的デリバリ(CD)」の組み合わせて、新しいバージョンのソフトウェアを提供するために実行する一連のステップであり、それらを自動化するものである。一つ目の継続的インテグレーションとは、別々で稼働している複数エンジニアのソースコードを継続的に統合して、それらが正常に動作することを確認し検証する取り組みである。新しいソースコードを変更が、PRマージされたタイミングもしくは定期的にビルドおよびテストする。そのため、手作業でテストを実行する必要がなくなる。二つ目の継続的デリバリーとは、ユーザに継続的にアプリケーションを提供することをさす。検証済みコードのリポジトリへのリリースを自動化する。コード変更のマージから本番環境に対応するためのビルドのデリバリーまで、全ての段階でテストをコードリリースを自動化する。そのため、運用チーム本番環境にアプリケーションをすぐにデプロイできる。
GitHub Actions
GitHubが2019年11月にリリースした新機能。上記で説明したCI/CDを実現するため、GitHubが提供している機能である。GitHub上のリポジトリに対するさまざまな操作をトリガーとして、あらかじめ定義しておい多処理を自動実行できる。例えば、今まで外部サービスとの連携が必要だった自動テストや自動ビルドが、GitHubだけで実現可能となる。「git pushした際に自動的に環境ビルドして、実環境にデプロイする」とか「自動テストを定義しておき、定期的に環境に対して実行する」といった操作を行える。
Herokuサーバ
Herokuとは、「アプリケーションをデプロイすることで、自動でアプリ公開ができる」というプラットフォームである。「ファイルをアップロードしたらサイトが公開される」という意味で、共有レンタルサーバと似ている。両者で大きく違うのは、①機能、②スケーリング、③料金体系である。
- ①機能:レンタルサーバは、基本的にPHPだけしか使えない。一方で、HerokuはRuby、Java、PHP、Python、Nodeといったさまざまな言語に対応している。DBについても、レンタルサーバはMySQLがメインである一方で、HerokuはPostgreSQLとなる。
- ②スケーリング:Webサーバは負荷がかかるとサーバ動作が遅くなったりダウンしてしまう。そういった状況を回避するために、Herokuは負荷状況に応じてサーバの規模を変更する「スケーリング」を行うことができる。
- ③料金体系:レンタルサーバは「初期費用」と「月額費用」がかかるが、Herokuは「初期費用」が不要で月々の「処理量」に応じてコストがかかる。
ハンズオン
GitHubレポジトリの設定
まずはGitHubページで、任意のリポジトリを作成する。
その後、PCのターミナルを開いて、プロジェクトフォルダを作成する。作成したフォルダ直下で、git cloneを実行して、GitHubレポジトリの設定を行う。
Pythonの仮想環境を構築
下記の通りコマンドを打って、pythonのvenvを設定することで、pythonの仮想環境を作成しアクティブ化する。pythonの仮想環境を利用する理由については、pythonパッケージのバージョン管理や依存関係の管理を簡単にするためである。例えば、異なるバージョンのpythonを使い分けることができる。また、システム全体で利用しているpythonの環境に影響しないように、仮想環境内のモジュール追加や入れ替えを行うことができる。
# myenv という名前の仮想環境を作成する
python3 -m venv myenv
# myenv という名前の仮想環境をアクティブにする
source myvenv/bin/activate
# flaskとpytestをインストールする
pip install flask pytest
# Python仮想環境でインストールされているすべてのパッケージのリストをファイルに書き出すために使用する。
# "pip freeze"コマンドは、仮想環境内のすべてのパッケージとそのバージョンを表示し、">"演算子は、この出力を"requirements.txt"という名前のファイルにリダイレクトするために使用される。
pip freeze > requirements.txt
インストールしたrequirements.txファイルは、下記内容に書き換える
flask
pytest
Djangoアプリを開発
app.pyとtest_app.pyファイルを作成する。app.pyは "Hello, world!" を返すプログラムで、test_app.pyはそのメッセージが正しく出力されるか、チェックするためのプログラムである。このテストプログラムは、CI/CDパイプラインで継続的にソフトウェアのインテグレーションをする上で、システム品質が担保されているか、チェックするためのユニットテストのようなイメージである。最後に、ファイルを諸々追加しているので、フォルダ構成に誤りがないように共有する。
プロジェクトフォルダ/
├ myvenv/
├ src/
│ └ app.py
├ tests/
│ └ test_app.py
└ requirements.txt
app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Hello, world!"
if __name__ == "__main__":
app.run()
test.py
from app import index
def test_index():
assert index() == "Hello, world!"
上記ファイルを作成後、下記コマンドを実行する。pytestでユニットテストを実行して、問題が発生しないか確認するため、テスト対象のapp.pyがあるsrcフォルダにパスを設定する必要がある。テストが問題なく実行されれば、添付画像が表示される。
export PYTHONPATH=src
pytest
.gitignoreファイルを作成
.gitignoreをルートフォルダに作成する。GitHubにファイルをpushする上で、Gitでコード管理したくないファイルやディレクトリを指定するためのファイルである。例えば、個人PC環境に依存するファイルやパスワード関連の重要なファイルは公開したくないので、これらを指定する必要がある。また、nodeモジュールといったディレクトリの使用にも使う。
myvenv/
__pycache__/
最後に、下記コマンドを実行することで、GitHubにコードをpushできる。
git add .
git commit -m "Initial commit"
git push
GitHub Actionsを設定する
Actionsタブから、Python applicationを選択する。
そうすると、下記フォルダにファイルが作成される。pythonapp.ymlファイルについて、ファイルについて、簡単に説明する。概要を説明すると、下記コードは、GitHub Actionsを使用してPythonアプリケーションをビルド、テスト、およびデプロイするためのワークフローを定義している。このワークフローは、GitHub上のリポジトリのpushイベントがトリガーとなっている。
プロジェクトフォルダ/.github/workflows/pythonapp.yml
# workflowの名前が、python applicationであることを宣言
name: Python application
# GitHubのpushイベントをトリガー設定
on: [push]
# jobを設定している。
jobs:
build:
# ubuntu-latestシステムで、workflowが実行される
runs-on: ubuntu-latest
# workflowを設定している
steps:
# ubuntu-latestのレポジトリをチェックしている
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
# pythonアップデートとrequirements.txtのインストールが実行されるよう設定
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
# pythonのsytaxエラーやコードエラーをチェックするためのソフトウェア
- name: Lint with flake8
run: |
pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
# ユニットテストを実行するため、pytestをインストール・実行。こうすることで、GitHubレポジトリで新しいpushがある度に、pytestによるユニットテストでチェックする。
- name: Test with pytest
run: |
pip install pytest
export PYTHONPATH=src
pytest
最後に、下記コマンドを実行することで、GitHubのコードを更新できる。そうすると、添付の通りActionsで正しくアプリケーションがデプロイされていることが、確認できるはずである。
git add .
git commit -m "Update django app"
git pull --rebase
git push
Heroku CLIのインストール
Herokuコマンドを使用するので、CLIをインストールする。
Herokuコマンドの実行
下記コマンドを実行して、herokuにログインし新規プロジェクトを作成する。そうすると、アプリケーションURLが発行される。
heroku login
heroku create
ここで、プロジェクトフォルダにて下記コマンドを実行すると、herokuにアプリケーションがデプロイされる。ただし、アプリケーションの更新があるたびに、手動で下記コマンドを実行しデプロイする必要がある。今回のハンズオンでは、CI/CDパイプラインを構築することで、アプリケーションの更新があったら、自動でその更新内容をチェックして、herokuサーバに対しデプロイすることが目的である。そのため、このプロセスを自動化できるように、以降説明を進める。
git push heroku master
heroku情報をGitHub actionsで設定
ここで、一点疑問が浮かぶ。CI/CDパイプラインを構築する上で、アプリケーションの更新があった際に、どのようなワークフローを実行すべきか、pythonapp.ymlで定義した。ただ、そのワークフローを実行する先であるherokuサーバの情報については、どのように記載すべきか?現状だと、何も書かれていない。また、herokuサーバの個人情報についてpythonapp.ymlに記載すると、重要な機密情報までGitHubで公開されてしまう。ここで使用するのが、Heroku APIである。
heroku authorization tokenを作成
下記コマンドで、Herokuのauthorization tokenを作成する
heroku authorizations:create
authorization tokenをGitHubで設定
下記画面で、secretsを設定する。設定方法は、「New repository secret」をクリックして、Nameにsecret名、そしてSecretに上記添付のtokenを記載する。
今回は、api_tokenとapp_nameの二つについて、設定した。
api_token
app_name
pythonapp.ymlファイルにheroku情報を記載
最後に、pythonapp.ymlの末尾にheroku情報を記載する。
- name: Deploy to Heroku
env:
HEROKU_API_TOKEN: ${{ secrets.HEROKU_API_TOKEN }}
HEROKU_APP_NAME: ${{ secrets.HEROKU_APP_NAME }}
if: github.ref == 'refs/heads/master' && job.status == 'success'
run: |
git remote add heroku https://heroku:$HEROKU_API_TOKEN@git.heroku.com/$HEROKU_APP_NAME.git
git push heroku HEAD:master -f
Procfile(プロセスファイル)を作成
Herokuなどのプラっフォームで使用される、アプリケーションのプロセスを宣言するためのファイルである。Pythonアプリケーションを実行するため、プロジェクトフォルダ直下にProcfile(*拡張子なし)を作成し下記記載をする。
web gunicorn --pythonpath src app:app
上記コードについて、どのような意味か記載する。
- web:プロセス名を指定する
- gunicorn:アプリケーション実行のため、Python webサーバを指定する
- -- pythonpath src:Pythonのインポートパスを指定する。この場合、srcディレクトリ直下のappファイルを指定している。
- 一つ目のappがpythonのモジュール名で、二つ目のappがflaskアプリケーションのインスタンス名である。これにより、gunicornがapp変数を見つけて、flaskアプリケーションを起動できる。
runtime.txtファイルを作成
最後に下記内容で、プロジェクトフォルダ直下にruntime.txtファイルを作成する。このファイルは、Pythonアプリケーションをデプロイする際に使用されるファイルである。アプリケーションが実行される際に使用するPythonのバージョンを指定する。例えば、Python3.7で動作する場合、下記のように記載する。このファイルがあれば、Herokuのようなデプロイメントプラットフォームで、このファイルを読み取り、指定されたPythonのバージョンを使用してアプリケーションを実行するよう設定できる。これにより、アプリケーションが正しく動作して、互換性の問題を回避できる。
python-3.7.6
また、requirements.txtファイルを記載内容を変更する。
flask
pytest
gunicorn
そしてコードをpushする
git add .
git commit -m "Update django app"
git push
そうすると、Deploy to Herokuでエラーが発生する。
Shallow clone
上記画像の通り、shallow cloneによるエラーが発生する。Shallow cloneとは、GitHubリポジトリの一部分だけをクローンすることである。通常、GitHubリポジトリをクローンする場合、全てのコミット履歴やブランチが含まれる。しかし、Shallow cloneを使用すると、リポジトリ全体ではなく、一部のみクローンされるので、クローン時間とディスクスペースの使用量を削減できる。
そのため、GitHub checkoutにて、"Fetch all history for all tags and branches" を実行する。
下記のようにコードを追加する。
- run: |
git fetch --prune --unshallow
そして、再度コードをpushする。
git add .
git commit -m "Update django app"
git push
herokuにデプロイ
上記の通り、pushするとherokuにデプロイされる。
デプロイが完了すると、herokuサーバにてアプリケーションが表示される。
参考
Discussion