PythonとCircleCIで自動テストとHerokuへの自動デプロイに入門する
はじめに
この記事はpipenvの環境で、pythonのテストモジュールであるpytestを、circleCI上で実行し、その後Herokuへの自動デプロイを行う方法を具体例を用いてまとめた記事です。
なお、WSL環境(ubuntu) + pipenvは導入済みであるものとします。
導入方法についてはこちらの記事などを参考にされてください。
また、恐らくDocker環境を使いますので、Docker for Windowsも必要かもしれません。
CircleCIについて
CircleCIは継続的インテグレーション(CI)や継続的デリバリー(CD)を実現するためのサービスです。
GithubなどのVCSと連携することで、開発したコードをプッシュするたびにビルドやテスト、あるいはデプロイといった一連の処理を自動化することができます。
これによって開発に付随する処理の属人化を防いだり、開発サイクルを素早く実行することができます。
今回はこのCircleCIに簡単なPythonアプリケーションで入門してみましょう。
前準備
まず初めに、今回の作業用のディレクトリを作って、pytestをインストールしておきましょう。
mkdir python_circleci # (適切なフォルダで)作業ディレクトリを作成する
cd python_circleci # 移動
pipenv --python 3 # pythonの環境を初期化する
pipenv install pytest # pytestをインストールする
code . # VScodeで開く
次に、適当な関数を用意して、その関数をテストする関数を作ります。
ソースコードの作成
テストやデプロイを行うための対象となるコードを作成します。
srcフォルダを作り、その中にcalc.pyを作ってください。単純に四則演算の関数でも書きましょう。
def plus(a, b):
return a + b
def sub(a, b):
return a - b
def mul(a, b):
return a * b
def div(a,b):
return a // b
また、testフォルダを作り、その中にtest_calc.pyを作ってください。
from src.calc import * # 関数を読み込む
def test_mul():
assert 6 == mul(3, 2)
def test_div():
assert 6 == div(12,2)
また、それぞれのフォルダに__init__.pyというファイルを追加してください。これはモジュールのインポートの解決に必要となります。
# 空でOK
ちなみに現時点ではフォルダ構成はこうなります。
.
├── Pipfile
├── Pipfile.lock
├── src
│ ├── __init__.py
│ └── calc.py
└── test
├── __init__.py
└── test_calc.py
自動テストの作成
まず最初にローカルでpytestを実行してみましょう。
# 現在の環境にインストールされたpytestを実行する
# pipenv run pytest test_app.pyだとtest_app.pyを実行するが、
# 引数なしだと自動でtest_と名の付くスクリプトを実行する
pipenv run pytest
ちゃんとテストが実行されていますね。(画像はテストコードが一つのものです)
CircleCIの設定ファイルの作成
次にcircleCIの設定ファイルを作成しましょう。
参考: https://circleci.com/docs/ja/2.0/language-python/
.circleci
というフォルダを作って、その中にconfig.ymlを作ります。
version: 2
jobs: # jobは処理のまとまり、実行環境やいくつかの処理(step)で構成される
build: # job名
docker: # circleci上でのdockerによる実行環境の指定
- image: cimg/python:3.10.2 # 自分の実行環境に合わせる cimgのpythonであればpipenvがインストール済み
steps:
- checkout # ソースコードをgitからcheckoutする
- run: python --version
- run:
name: install
command: pipenv install # Pipfileを用いてインストールする
- run:
name: run test
command: pipenv run pytest --junitxml=test-reports/junit.xml # pytest実行の際にテスト結果のレポートを作成する
- store_test_results: # テスト結果をCircleCIにアップロード
path: test-reports
ローカルでCircleCIを動かす
この時点でローカルでcircleCIを動かしてみましょう。そのためのCLIが提供されているのでインストールします。
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | bash
また、checkoutのステップを実行するために、この時点でgitで管理しておきましょう。
git init
git branch -M main
git add .
git commit -m "first commit"
設定ファイルを確認しましょう
circleci config validate .circleci/config.yml
次はjobを実行してみましょう
circleci local execute --job build
最後の結果を保存するところについてはパスを用意していないのでうまく動かないかと思いますが、他の部分は動いてますね。
GithubとCircleCIの連携
Githubにレポジトリを作ってpushする
それでは次はこのソースファイルをgithub上におき、そのレポジトリをCicleCIと連携しましょう。
github上でレポジトリを作成してから、以下のコマンドでpushしましょう。
git remote add origin git@github.com:ユーザー名/レポジトリ名.git
git push -u origin main
その後、CircleCI( https://circleci.com/ja/ )にログインしてください。アカウントがなければアカウントを作成してください。
githubと連携させると、レポジトリの一覧がでると思うので、今作ったレポジトリをsetupします。
すると、どのconfig.ymlを使うかという設定項目がでてきます。今回はすでにconfig.ymlを用意しているのでmainブランチのものを使いましょう。
連携した後で今セットアップしたプロジェクトを見に行くと、設定した一連の処理が実行されているのが分かります。
テストを失敗してみる
次にテストを失敗してみましょう。以下のようなテストを追加して、pushしてみてください。
def test_plus():
assert 4 == plus(1,1) # 間違えたテスト
その後、プロジェクトを確認すると、テストがちゃんと失敗していることがわかります。
自動デプロイ
これまでで、継続的にテストする方法は分かったので、次は継続的にデプロイする方法を見ていきましょう。
まずはサーバーを構築します。pythonには標準ライブラリとして軽量なhttpサーバーのモジュールがついていますので、そちらを使います。
# coding: utf-8
from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler
from calc import *
class class1(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("User-Agent","test1")
self.end_headers()
# 折角なので計算もさせる
html = "<h1>Answer is " + str(mul(6,7)) + "</h1>"
self.wfile.write(html.encode())
ip = '127.0.0.1'
port = 8765
server = HTTPServer((ip, port), class1)
print("access http://127.0.0.1:8765")
server.serve_forever()
Pipfileに実行コマンドを登録しましょう
[scripts]
serve = "python src/server.py"
すると以下のコマンドでサーバーを実行できます。ブラウザで( http://127.0.0.1:8765 )にアクセスしてみてください。
pipenv run serve
全ての宇宙の答え
Herokuにアプリをデプロイする
まずは手動でデプロイを行います。
Heroku側の設定をしましょう。herokuで新規アプリを作成してください。
アプリ名はなんでも構いません。
その後、少しソースコードを書き換えます。
herokuはポート番号を動的に割り当てるので、ポート番号を8765から下記のように書き換えましょう。
import os
# 中略
ip = '0.0.0.0'
# PORTという環境変数からherokuが割り当てたポート番号を取得する
port = int(os.environ.get('PORT', 8765))
server = HTTPServer((ip, port), class1)
print("Access http://0.0.0.0:" + str(port))
次に、Heroku上のアプリケーションを起動するためのコマンドの指定として、Procfileを作成します。下記のようにファイルを作成し、プロジェクトフォルダ直下に配置してください。
web: pipenv run serve
Herokuに手動でデプロイを行うために、heroku cliをローカルにインストールします。
curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
その後、herokuにログインしてください
heroku login # herokuにログインする
heroku apps # アプリ名の一覧を表示する
heroku git:remote -a アプリ名 # 先ほど作成したアプリ名へのデプロイ用レポジトリを追加する
git push heroku main # heroku上にデプロイする
すると、無事にデプロイされ、アクセスすると数字が出るはずです
CircleCIによるデプロイの自動化
これらの作業を自動化することで、最新版のソフトウェアをいつもユーザーが使うことができるようになります。
また、このデプロイプロセスを繰り返すことによって、アプリケーションをユーザーに提供できなくなるような破壊的な変更に早く気付くことが出来ます。
CircleCIからHerokuへのデプロイにはOrbを用いると便利です。
これはCircleCIが予め用意しているワークフローで、プリセットのようなものです。
Heroku用のOrbがあります(参考: https://circleci.com/developer/orbs/orb/circleci/heroku )
まず、config.ymlを下記のように修正しましょう。
version: 2.1
orbs: # heroku用のorbを使う
heroku: circleci/heroku@1.2.6
jobs:
deploy:
executor: heroku/default
steps:
- checkout
- heroku/install
- run:
command: >
echo "The command above installs Heroku, the command below deploys.
What you do inbetween is up to you!"
- heroku/deploy-via-git
build-test:
docker: # circleci上でのdockerによる実行環境の指定
- image: cimg/python:3.10.2 # 自分の実行環境に合わせる cimgのpythonであればpipenvがインストール済み
steps:
- checkout
- run: python --version
- run:
name: install
command: pipenv install # Pipfileを用いてインストールする
- run:
name: run test
command: pipenv run pytest --junitxml=test-reports/junit.xml
- store_test_results: # テスト結果をCircleCIにアップロード
path: test-reports
workflows:
version: 2.1
test_and_deploy: # workflow名
jobs:
- build-test
- deploy:
requires: # buildが成功したら実行する
- build-test
次に、HerokuへアクセスするためのAPI keyとアプリの名前をCircleCIのプロジェクト設定から設定しましょう。
設定するのはHEROKU_API_KEY
とHEROKU_APP_NAME
の二つの環境変数です。
HEROKU_API_KEY
については
# heroku login 後
heroku auth:token
で取得することができます。
また、HEROKU_APP_NAME
はHeroku上のアプリ名です。
ではこれを使ってみようと思うので、少しだけget時の処理を書き換えて、pushしましょう。
# 中略
# 折角なので計算もさせる
- html = "<h1>Answer is " + str(mul(6,7)) + "</h1>"
+ html = "<h1>Answer is " + str(mul(5,5)) + "</h1>"
# コミットしてから
git push origin main
すると、デプロイが失敗したのではないでしょうか?
実は、まだ間違えているテストをそのままにしていたので、テストが通らなかったのです。下記のように、requiresを指定していることで、テストが通ったときだけデプロイ処理を行うように指定することができます。
- deploy:
requires: # buildが成功したら実行する
- build-test
間違えたテストを修正して、再度プッシュを行いましょう。
def test_plus():
- assert 4 == plus(1,1) # 間違えたテスト
+ assert 2 == plus(1,1) # 正しいテスト
# コミットしてから
git push origin main
build-testもdeployも正しく実行されていることが分かる
HerokuでOpen Appからアクセスしてみましょう。
これでデプロイを自動化することができました!
終わりに
以上までの方法で、Pythonのアプリケーションに対して、Githubと連携し自動テストと自動デプロイをさせることができるようになりました。
より学びたい方のために、追加課題を書いておきたいと思います。
- Slack通知と連携させる( https://circleci.com/developer/ja/orbs/orb/circleci/slack )
- キャッシュ化機能を使ってCircleCIを効率的に動かす( https://circleci.com/docs/ja/2.0/caching/ )
といった方法にチャレンジしてみてください。
また、もしこの記事が役に立てば、食料品などを買っていただけると幸いです。
Discussion