はじめに
前回の記事では、Dockerfile を使って Flask + MySQL アプリケーションをコンテナ化しました
しかし、コードを変更するたびに手動でビルド・デプロイするのは非効率です
この記事では、GitHub Actions を使って、コードを push するだけで自動的にビルド・テスト・デプロイが実行される CI/CD パイプラインを構築します
この記事で分かること
- CI/CD とは何か、なぜ必要なのか
- GitHub Actions の基本的な使い方
- Docker イメージの自動ビルドと Docker Hub への push
- 自動テストの実行方法
- AWS EC2 への自動デプロイ
- 実践:Flask アプリの完全な CI/CD パイプライン構築
前提条件
本記事は、以下が完了していることを前提としています
- GitHub アカウントを持っている
- Docker Hub アカウントを持っている
- AWS アカウントを持っている(無料枠で OK)
- 基本的な Git の操作ができる
- Docker と Docker Compose の基本を理解している
前提記事:
- Docker Desktop for Windows 入門:仕組みと構築方法
- Docker Compose を使った複数コンテナの管理
- Dockerfile を使った独自イメージの作成:Flask + MySQL で Web アプリを構築
なお、本記事で使用するソースコードは、以下のGitHubリポジトリからダウンロードできます
flask-pipeline-app(GitHubリポジトリ)
CI/CD とは?
従来の開発フロー(手動)
コード変更
↓
手動でテスト実行
↓
手動でビルド
↓
手動でサーバーにデプロイ
↓
動作確認
問題点:
- 時間がかかる
- 人的ミスが発生しやすい
- デプロイが億劫になる
CI/CD の開発フロー(自動)
コード変更 → Git push
↓
自動でテスト実行
↓
自動でビルド
↓
自動でデプロイ
↓
自動で通知
メリット:
- 高速なフィードバック
- 人的ミスの削減
- 頻繁なデプロイが可能
CI/CD の用語
| 用語 | 説明 | 例 |
|---|---|---|
| CI (Continuous Integration) | 継続的インテグレーション | コードを push したら自動テスト |
| CD (Continuous Delivery) | 継続的デリバリー | テスト通過後、自動でビルド |
| CD (Continuous Deployment) | 継続的デプロイメント | ビルド後、自動で本番環境へデプロイ |
GitHub Actions とは?
GitHub が提供する CI/CD プラットフォームです
主な特徴
- GitHub に統合されている(追加設定不要)
- YAML ファイルで設定
- 豊富なアクション(再利用可能な処理)
- 無料枠あり(パブリックリポジトリは無制限)
GitHub Actions の基本構造
name: ワークフロー名
on:
push:
branches: [ main ] # トリガー(main ブランチへの push)
jobs:
job-name:
runs-on: ubuntu-latest # 実行環境
steps:
- name: ステップ名
uses: actions/checkout@v3 # アクションの使用
- name: コマンド実行
run: echo "Hello World" # コマンドの実行
実践:Flask アプリの CI/CD パイプライン構築
今回の構成
この記事では、以下の構成で CI/CD パイプラインを構築します
[開発者]
↓ Git push
[GitHub]
↓ トリガー
[GitHub Actions] ← CI/CD エンジン(テスト・ビルド・デプロイを自動実行)
↓ Docker イメージを push
[Docker Hub] ← イメージレジストリ(ビルドしたイメージを保管)
↓ イメージを pull
[AWS EC2] ← 単なる Linux サーバー(Docker が動く環境)
↓
[ユーザー] ← ブラウザでアクセス
AWS の役割
| 項目 | 説明 |
|---|---|
| 使用するもの | EC2(仮想マシン)のみ |
| 役割 | Docker と Docker Compose が動く Linux 環境を提供 |
| 位置づけ | 単なる Web サーバー |
AWS 固有の機能は使用しない
以下の AWS サービスは使用しません
- ❌ AWS CodePipeline(CI/CD サービス)
- ❌ AWS CodeBuild(ビルドサービス)
- ❌ AWS CodeDeploy(デプロイサービス)
- ❌ ECS/Fargate(コンテナオーケストレーション)
- ❌ ECR(Docker イメージレジストリ)
理由:
シンプルで理解しやすい
無料枠で完結
他の VPS サービスでも同じ構成が可能
※AWS 無料利用枠の注意点
t2.micro インスタンス: 月 750 時間まで無料(1インスタンスを常時稼働可能)
ストレージ: 30 GB まで無料
データ転送: 月 15 GB まで無料(アウトバウンド)
無料期間: AWS アカウント作成から 12 ヶ月間
EC2 の代替サービス
Docker が動く Linux サーバーがあれば、EC2 の代わりに以下も使用可能です
- さくらの VPS
- ConoHa VPS
- DigitalOcean Droplet
- Oracle Cloud(永久無料)
完成イメージ
Git push (main ブランチ)
↓
GitHub Actions 起動
↓
① コードのチェックアウト
↓
② Python 環境のセットアップ
↓
③ 依存パッケージのインストール
↓
④ テストの実行
↓
⑤ Docker イメージのビルド
↓
⑥ Docker Hub へ push
↓
⑦ デプロイ(AWS EC2)
↓
完了通知
ステップ1:プロジェクトの準備
ディレクトリ構成
以下の構成でプロジェクトを作成します
flask-pipeline-app/
├── .github/
│ └── workflows/
│ └── ci-cd.yml
├── app/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── app.py
│ ├── test_app.py
│ └── templates/
│ └── index.html
├── docker-compose.yml
├── docker-compose.prod.yml
└── init.sql
プロジェクトディレクトリの作成
# プロジェクトディレクトリを作成
mkdir flask-pipeline-app
cd flask-pipeline-app
# サブディレクトリを作成
mkdir app
mkdir app\templates
mkdir .github
mkdir .github\workflows
ディレクトリとファイルの作成
# プロジェクトディレクトリに移動(既に作成済み)
cd flask-pipeline-app
# 各ファイルを作成
# Windows の場合
type nul > .github\workflows\ci-cd.yml
type nul > app\Dockerfile
type nul > app\requirements.txt
type nul > app\app.py
type nul > app\test_app.py
type nul > app\templates\index.html
type nul > docker-compose.yml
type nul > docker-compose.prod.yml
type nul > init.sql
# または、エディタで直接作成
code .
これから各ファイルの内容を作成していきます
ステップ2:Webアプリケーション(Flask版)の作成
app/requirements.txt
Python の依存パッケージを定義します
Flask==3.0.0
mysql-connector-python==8.2.0
pytest==7.4.3
pytest-cov==4.1.0
app/app.py
Flask アプリケーションのメインファイルです
from flask import Flask, render_template, request, redirect, url_for
import mysql.connector
import os
import time
app = Flask(__name__)
# MySQL 接続設定(環境変数から取得)
db_config = {
'host': os.getenv('DB_HOST', 'db'),
'user': os.getenv('DB_USER', 'flaskuser'),
'password': os.getenv('DB_PASSWORD', 'flaskpass'),
'database': os.getenv('DB_NAME', 'flaskdb')
}
def get_db_connection():
"""MySQL への接続を取得(リトライ機能付き)"""
max_retries = 5
retry_interval = 2
for attempt in range(max_retries):
try:
conn = mysql.connector.connect(**db_config)
return conn
except mysql.connector.Error as err:
if attempt < max_retries - 1:
print(f"データベース接続失敗(試行 {attempt + 1}/{max_retries}): {err}")
time.sleep(retry_interval)
else:
raise
@app.route('/')
def index():
"""ユーザー一覧を表示"""
try:
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT * FROM users ORDER BY created_at DESC')
users = cursor.fetchall()
cursor.close()
conn.close()
return render_template('index.html', users=users, error=None)
except Exception as e:
return render_template('index.html', users=[], error=str(e))
@app.route('/add', methods=['POST'])
def add_user():
"""新規ユーザーを追加"""
name = request.form.get('name')
email = request.form.get('email')
if not name or not email:
return redirect(url_for('index'))
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute(
'INSERT INTO users (name, email) VALUES (%s, %s)',
(name, email)
)
conn.commit()
cursor.close()
conn.close()
except Exception as e:
print(f"ユーザー追加エラー: {e}")
return redirect(url_for('index'))
@app.route('/delete/<int:user_id>')
def delete_user(user_id):
"""ユーザーを削除"""
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('DELETE FROM users WHERE id = %s', (user_id,))
conn.commit()
cursor.close()
conn.close()
except Exception as e:
print(f"ユーザー削除エラー: {e}")
return redirect(url_for('index'))
@app.route('/health')
def health():
"""ヘルスチェック用エンドポイント"""
try:
conn = get_db_connection()
conn.close()
return {'status': 'healthy', 'database': 'connected'}, 200
except Exception as e:
return {'status': 'unhealthy', 'error': str(e)}, 503
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
app/templates/index.html
ユーザーインターフェースです(前回の記事と同じ内容のため省略)
詳細は 前提記事:Dockerfile を使った独自イメージの作成 を参照してください
app/Dockerfile
Flask アプリケーションをコンテナ化します
# Python 3.11 の軽量イメージをベースに使用
FROM python:3.11-slim
# 作業ディレクトリを設定
WORKDIR /app
# 依存パッケージファイルをコピー
COPY requirements.txt .
# Python パッケージをインストール
RUN pip install --no-cache-dir -r requirements.txt
# アプリケーションファイルをコピー
COPY . .
# Flask が使用するポートを公開
EXPOSE 5000
# コンテナ起動時に Flask アプリを実行
CMD ["python", "app.py"]
ステップ3:MySQL 初期化スクリプトの作成
init.sql
データベースとテーブルを自動作成します
-- データベースが存在しない場合は作成
CREATE DATABASE IF NOT EXISTS flaskdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- データベースを使用
USE flaskdb;
-- users テーブルを作成
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- サンプルデータを挿入
INSERT INTO users (name, email) VALUES
('YamadaTaro', 'taro@example.com'),
('SatoHanako', 'hanako@example.com'),
('SuzukiJiro', 'jiro@example.com')
ON DUPLICATE KEY UPDATE name=name;
ステップ4:Docker Compose の設定
docker-compose.yml
開発環境用の設定です
version: '3.8'
services:
db:
image: mysql:8.0
container_name: flask-mysql-db
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: flaskdb
MYSQL_USER: flaskuser
MYSQL_PASSWORD: flaskpass
FLASK_ENV: development
volumes:
- db-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- flask-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpass"]
interval: 10s
timeout: 5s
retries: 5
restart: always
web:
build:
context: ./app
dockerfile: Dockerfile
container_name: flask-web
ports:
- "5000:5000"
environment:
DB_HOST: db
DB_USER: flaskuser
DB_PASSWORD: flaskpass
DB_NAME: flaskdb
volumes:
- ./app:/app
networks:
- flask-network
depends_on:
db:
condition: service_healthy
restart: always
volumes:
db-data:
networks:
flask-network:
driver: bridge
ステップ5:テストコードの作成
app/test_app.py
Flask アプリケーションのテストを作成します
import pytest
from app import app
import os
@pytest.fixture
def client():
"""テスト用のクライアントを作成"""
app.config['TESTING'] = True
# テスト用の環境変数を設定
os.environ['DB_HOST'] = 'localhost'
os.environ['DB_USER'] = 'testuser'
os.environ['DB_PASSWORD'] = 'testpass'
os.environ['DB_NAME'] = 'testdb'
with app.test_client() as client:
yield client
def test_health_endpoint(client):
"""ヘルスチェックエンドポイントのテスト"""
response = client.get('/health')
# ステータスコードが 200 または 503 であることを確認
assert response.status_code in [200, 503]
# JSON レスポンスであることを確認
assert response.content_type == 'application/json'
# status フィールドが存在することを確認
data = response.get_json()
assert 'status' in data
def test_index_page(client):
"""トップページのテスト"""
response = client.get('/')
# ステータスコードが 200 であることを確認
assert response.status_code == 200
# HTML が返されることを確認
assert b'<!DOCTYPE html>' in response.data
def test_add_user_validation(client):
"""ユーザー追加のバリデーションテスト"""
# 空のデータで POST
response = client.post('/add', data={})
# リダイレクトされることを確認
assert response.status_code == 302
ステップ6:本番用 Docker Compose の作成
docker-compose.prod.yml
本番環境用の設定を作成します
version: '3.8'
services:
db:
image: mysql:8.0
container_name: flask-mysql-db-prod
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
FLASK_ENV: production
volumes:
- db-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- flask-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
restart: always
web:
image: ${DOCKER_USERNAME}/flask-pipeline-app:latest # Docker Hub から取得
container_name: flask-web-prod
ports:
- "80:5000"
environment:
DB_HOST: db
DB_USER: ${MYSQL_USER}
DB_PASSWORD: ${MYSQL_PASSWORD}
DB_NAME: ${MYSQL_DATABASE}
FLASK_ENV: production
networks:
- flask-network
depends_on:
db:
condition: service_healthy
restart: always
volumes:
db-data:
networks:
flask-network:
driver: bridge
ステップ7:GitHub Actions ワークフローの作成
.github/workflows/ci-cd.yml
CI/CD パイプラインを定義します
name: Flask App CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
DOCKER_IMAGE_NAME: flask-pipeline-app
jobs:
test:
name: テストの実行
runs-on: ubuntu-latest
steps:
- name: コードのチェックアウト
uses: actions/checkout@v3
- name: Python 環境のセットアップ
uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
- name: 依存パッケージのインストール
run: |
cd app
pip install -r requirements.txt
- name: テストの実行
run: |
cd app
pytest test_app.py -v --cov=app --cov-report=term-missing
- name: テスト結果のアップロード
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: app/.coverage
build:
name: Docker イメージのビルドと push
runs-on: ubuntu-latest
needs: test # test ジョブが成功したら実行
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: コードのチェックアウト
uses: actions/checkout@v3
- name: Docker Buildx のセットアップ
uses: docker/setup-buildx-action@v2
- name: Docker Hub へログイン
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Docker イメージのビルドと push
uses: docker/build-push-action@v4
with:
context: ./app
file: ./app/Dockerfile
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:latest
${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:${{ github.sha }}
cache-from: type=registry,ref=${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:buildcache,mode=max
- name: イメージ情報の出力
run: |
echo "Image: ${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:latest"
echo "SHA: ${{ github.sha }}"
deploy:
name: 本番環境へのデプロイ
runs-on: ubuntu-latest
needs: build # build ジョブが成功したら実行
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: コードのチェックアウト
uses: actions/checkout@v3
- name: SSH 秘密鍵の設定
run: |
mkdir -p ~/.ssh
cat << 'EOF' > ~/.ssh/id_rsa
${{ secrets.SSH_PRIVATE_KEY }}
EOF
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts
- name: サーバーへファイルをコピー
run: |
scp docker-compose.prod.yml init.sql ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:~/flask-app/
- name: サーバーでデプロイを実行
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} << 'EOF'
cd ~/flask-app
# 環境変数を設定
export DOCKER_USERNAME=${{ secrets.DOCKER_USERNAME }}
export MYSQL_ROOT_PASSWORD=${{ secrets.MYSQL_ROOT_PASSWORD }}
export MYSQL_DATABASE=${{ secrets.MYSQL_DATABASE }}
export MYSQL_USER=${{ secrets.MYSQL_USER }}
export MYSQL_PASSWORD=${{ secrets.MYSQL_PASSWORD }}
# 最新のイメージを取得
docker pull ${{ secrets.DOCKER_USERNAME }}/flask-pipeline-app:latest
# コンテナを再起動
docker-compose -f docker-compose.prod.yml down
docker-compose -f docker-compose.prod.yml up -d
# 古いイメージを削除
docker image prune -f
EOF
- name: デプロイ完了通知
run: |
echo "デプロイが完了しました"
echo "URL: http://${{ secrets.SERVER_HOST }}"
ステップ8:GitHub Secrets の設定
GitHub リポジトリの Settings → Secrets and variables → Actions で以下の情報を登録します
このステップでは、以下の作業を行います
- Docker Hub アクセストークンの作成
- AWS EC2 インスタンスのセットアップ
- GitHub Actions 用 SSH 鍵の設定
- GitHub Secrets への登録
8-1. Docker Hub アクセストークンの作成
- Docker Hub にログイン
- Account Settings → Personal access tokens → [Generate New Token]をクリック
- トークン名を入力(例:
flask-pipeline-github-actions) - 権限を選択(Read, Write, Delete)
- Generate をクリック
- 表示されたトークンをコピー(再表示不可)
8-2. AWS EC2 インスタンスのセットアップ
1. EC2 インスタンスの作成
AWS マネジメントコンソールでの操作
- AWS マネジメントコンソール にログイン
- サービスから「EC2」を選択
- 「インスタンスを起動」ボタンをクリック
インスタンスの設定
| 項目 | 設定値 | 説明 |
|---|---|---|
| 名前 | flask-pipeline-server |
任意の名前 |
| AMI | Amazon Linux 2023 AMI | 無料利用枠の対象 |
| アーキテクチャ | 64 ビット (x86) | デフォルト |
| インスタンスタイプ | t2.micro | 無料利用枠の対象 |
| キーペア | 新規作成または既存 | SSH 接続用(後述) |
| ネットワーク設定 | デフォルト VPC | そのまま |
| セキュリティグループ | 新規作成(flask-pipeline-sg) | ルールを追加(後述) |
| ストレージ | 8 GB (gp3) | 無料利用枠の範囲内 |
セキュリティグループの設定
以下のインバウンドルールを追加します:
| タイプ | プロトコル | ポート範囲 | ソース | 説明 |
|---|---|---|---|---|
| SSH | TCP | 22 | 0.0.0.0/0 | SSH 接続用 |
| HTTP | TCP | 80 | 0.0.0.0/0 | Web アクセス用 |
| カスタム TCP | TCP | 5000 | 0.0.0.0/0 | Flask アプリ用(開発時) |
キーペアの作成に関して(初回のみ)
- 「新しいキーペアの作成」を選択
- キーペア名:
flask-pipeline-ec2-key - キーペアのタイプ:RSA
- プライベートキーファイル形式:.pem
- 「キーペアを作成」をクリック
- ダウンロードされた
flask-pipeline-ec2-key.pemを安全な場所に保存
重要: このキーペアは EC2 への SSH 接続用です(GitHub Actions 用の SSH 鍵とは別)
上記の設定を完了した後、「インスタンスを起動」ボタンをクリックして、EC2インスタンスを起動します
2. EC2 インスタンスへの接続
インスタンスの IP アドレスを確認
- EC2 ダッシュボードで「インスタンス」を選択
- 作成したインスタンスをクリック
- 「パブリック IPv4 アドレス」をメモ(例:
54.123.45.67)
SSH で接続
# Windows PowerShell で実行
# キーファイルのパーミッションを設定(初回のみ)
icacls flask-pipeline-ec2-key.pem /inheritance:r
icacls flask-pipeline-ec2-key.pem /grant:r "%USERNAME%:R"
# EC2 に接続(Amazon Linux 2023 のデフォルトユーザーは ec2-user)
ssh -i flask-pipeline-ec2-key.pem ec2-user@54.123.45.67
# 初回接続時は "yes" を入力
接続できれば、以下のようなプロンプトが表示されます
[ec2-user@ip-172-31-xx-xx ~]$
3. EC2 インスタンスに Docker をインストール
EC2 に SSH 接続した状態で以下を実行します
Amazon Linux 2023 では Docker のインストール手順
# システムパッケージを更新
sudo dnf update -y
# Docker をインストール
sudo dnf install -y docker
# Docker サービスを起動
sudo systemctl start docker
# Docker を自動起動に設定
sudo systemctl enable docker
# ec2-user を docker グループに追加
sudo usermod -aG docker ec2-user
# Docker Compose をインストール
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 設定を反映(再ログインが必要)
exit
再度 SSH で接続します
ssh -i flask-pipeline-ec2-key.pem ec2-user@54.123.45.67
インストール確認
# Docker のバージョン確認
docker --version
# 出力例: Docker version 24.0.7, build afdd53b
# Docker Compose のバージョン確認
docker-compose --version
# 出力例: Docker Compose version v2.23.0
4. デプロイ用ディレクトリの作成
# ホームディレクトリにアプリ用ディレクトリを作成
mkdir -p ~/flask-app
cd ~/flask-app
# 確認
pwd
# 出力: /home/ec2-user/flask-app
8-3. GitHub Actions 用 SSH 鍵の設定
1. ローカル PC (Windows) で GitHub Actions 用 SSH 鍵ペアを生成
重要: これは GitHub Actions が EC2 に自動デプロイするための鍵です(EC2 接続用のキーペアとは別)
# PowerShell で実行
# カレントディレクトリに鍵ファイルが作成されます
ssh-keygen -t rsa -b 4096 -C "flask-pipeline-github-actions" -f github-actions-key
# 実行結果:
# github-actions-key ← 秘密鍵(GitHub Secrets に登録)
# github-actions-key.pub ← 公開鍵(サーバーに登録)
パスフレーズを聞かれたら、Enter キーを2回押して空のままにします
2. 公開鍵を EC2 インスタンスに登録
公開鍵の内容を確認:
# Windows PowerShell で公開鍵の内容を表示
type github-actions-key.pub
出力例:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC... flask-pipeline-github-actions
この内容をコピーします
EC2 に SSH 接続して公開鍵を登録:
# EC2 に接続
ssh -i flask-pipeline-ec2-key.pem ec2-user@54.123.45.67
EC2 上で以下を実行:
# .ssh ディレクトリが存在しない場合は作成
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# authorized_keys ファイルを編集
nano ~/.ssh/authorized_keys
nano エディタが開いたら:
- コピーした公開鍵の内容を貼り付け(既存の内容を消さずに追加)
-
Ctrl + Oで保存 -
Enterで確認 -
Ctrl + Xで終了
パーミッションを設定:
chmod 600 ~/.ssh/authorized_keys
# 確認
cat ~/.ssh/authorized_keys
# 公開鍵の内容が表示されれば OK
# EC2 から一旦ログアウト
exit
3. 秘密鍵を GitHub Secrets に登録
秘密鍵の内容を確認:
# Windows PowerShell で秘密鍵の内容を表示
type github-actions-key
出力例:
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAr8... (長い文字列)
...
-----END RSA PRIVATE KEY-----
8-4. GitHub Secrets への登録
上記で取得した情報を GitHub Secrets に登録します
事前準備: GitHub リポジトリの作成
- GitHub にログイン
- 右上の「+」→「New repository」をクリック
- 以下を入力
- Repository name:
flask-pipeline-app - Public または Private を選択
- 「Add a README file」のチェックは外す
- Repository name:
- 「Create repository」ボタンをクリック
Secrets の登録手順
- 作成した GitHub リポジトリのページを開く
- 「Settings」タブをクリック
- 左メニューから「Secrets and variables」→「Actions」をクリック
- 「New repository secret」ボタンをクリック
- Name と Secret を入力
- 「Add secret」ボタンをクリック
- 上記の手順 4~6 を繰り返して、以下の 9 つの Secret を登録
登録する Secret 一覧
| Secret 名 | 値の例 | 取得方法 | 使用箇所と役割 |
|---|---|---|---|
DOCKER_USERNAME |
myusername |
Docker Hub のユーザー名 | Docker Hub へのログインとイメージの push に使用 |
DOCKER_PASSWORD |
dckr_pat_xxxxx |
Docker Hub で生成したアクセストークン(8-1 で作成) | Docker Hub への認証に使用 |
SERVER_HOST |
54.123.45.67 |
EC2 インスタンスのパブリック IPv4 アドレス(8-2 で確認) | SSH 接続先サーバーの IP アドレス |
SERVER_USER |
ec2-user |
Amazon Linux 2023 のデフォルトユーザー | SSH 接続時のユーザー名 |
SSH_PRIVATE_KEY |
-----BEGIN RSA... |
github-actions-key ファイルの内容(8-3 で作成) |
GitHub Actions から EC2 への SSH 接続に使用 |
MYSQL_ROOT_PASSWORD |
securepass123 |
任意の強力なパスワード | MySQL の root ユーザーのパスワード(docker-compose.prod.yml で使用) |
MYSQL_DATABASE |
flaskdb |
データベース名 | MySQL で作成するデータベース名(docker-compose.prod.yml で使用) |
MYSQL_USER |
flaskuser |
MySQL ユーザー名 | アプリケーションが使用する MySQL ユーザー(docker-compose.prod.yml で使用) |
MYSQL_PASSWORD |
flaskpass123 |
任意の強力なパスワード | アプリケーション用 MySQL ユーザーのパスワード(docker-compose.prod.yml で使用) |
ステップ9:GitHub リポジトリにコードの push
ローカルリポジトリの初期化
# プロジェクトディレクトリに移動
cd flask-pipeline-app
# Git リポジトリを初期化
git init
# .gitignore を作成
echo "__pycache__/
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.env
.vscode/
.coverage
*.log" > .gitignore
# ファイルを追加
git add .
# コミット
git commit -m "Initial commit: Flask + MySQL app with CI/CD"
# リモートリポジトリを追加
git remote add origin https://github.com/yourusername/flask-pipeline-app.git
# main ブランチに push
git branch -M main
git push -u origin main
ステップ10:CI/CD の動作確認
GitHub Actions の確認
- GitHub リポジトリの「Actions」タブを開く
- ワークフローが自動的に実行される
- 各ジョブの進行状況を確認
ワークフローの実行結果
✅ test (テストの実行)
├─ コードのチェックアウト
├─ Python 環境のセットアップ
├─ 依存パッケージのインストール
├─ テストの実行
└─ テスト結果のアップロード
✅ build (Docker イメージのビルドと push)
├─ コードのチェックアウト
├─ Docker Buildx のセットアップ
├─ Docker Hub へログイン
├─ Docker イメージのビルドと push
└─ イメージ情報の出力
✅ deploy (本番環境へのデプロイ)
├─ コードのチェックアウト
├─ SSH 秘密鍵の設定
├─ サーバーへファイルをコピー
├─ サーバーでデプロイを実行
└─ デプロイ完了通知
Docker Hub の確認
- Docker Hub にログイン
- Repositories を開く
-
flask-pipeline-appリポジトリが作成されている -
latestと<commit-sha>のタグが存在する - ブラウザで確認
http://your-server-ip
ステップ11:コード変更と自動デプロイの確認
コードを変更
# app/app.py の一部を変更
@app.route('/health')
def health():
"""ヘルスチェック用エンドポイント"""
try:
conn = get_db_connection()
conn.close()
return {
'status': 'healthy',
'database': 'connected',
'version': '1.1.0' # ← バージョンを追加
}, 200
except Exception as e:
return {'status': 'unhealthy', 'error': str(e)}, 503
変更を push
git add app/app.py
git commit -m "Add version to health endpoint"
git push origin main
自動デプロイの確認
- GitHub Actions が自動的に実行される
- テスト → ビルド → デプロイが順次実行される
- デプロイ完了後、ブラウザで確認
http://your-server-ip/health
レスポンス:
{
"status": "healthy",
"database": "connected",
"version": "1.1.0"
}
高度な設定
1. ブランチ戦略
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
# 全ブランチでテスト実行
build:
# main ブランチのみビルド
if: github.ref == 'refs/heads/main'
deploy-staging:
# develop ブランチはステージング環境へ
if: github.ref == 'refs/heads/develop'
deploy-production:
# main ブランチは本番環境へ
if: github.ref == 'refs/heads/main'
2. 通知の追加
Slack への通知を追加:
- name: Slack 通知
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'デプロイが完了しました'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
3. ロールバック機能
- name: ロールバック
if: failure()
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} << 'EOF'
cd ~/flask-app
docker-compose -f docker-compose.prod.yml down
docker pull ${{ secrets.DOCKER_USERNAME }}/flask-pipeline-app:previous
docker-compose -f docker-compose.prod.yml up -d
EOF
4. 環境ごとの設定
jobs:
deploy:
strategy:
matrix:
environment: [staging, production]
environment:
name: ${{ matrix.environment }}
url: https://${{ matrix.environment }}.example.com
トラブルシューティング
テストが失敗する
# ローカルでテストを実行
cd app
pytest test_app.py -v
# カバレッジを確認
pytest test_app.py --cov=app --cov-report=html
Docker イメージの push が失敗する
Error: denied: requested access to the resource is denied
対処法:
- Docker Hub のアクセストークンを確認
- GitHub Secrets の
DOCKER_USERNAMEとDOCKER_PASSWORDを確認 - Docker Hub でリポジトリが作成されているか確認
SSH 接続が失敗する
Permission denied (publickey)
対処法:
- SSH 秘密鍵が正しく設定されているか確認
- サーバーの
~/.ssh/authorized_keysに公開鍵が登録されているか確認 - サーバーのファイアウォール設定を確認
デプロイ後にアプリが起動しない
# サーバーにログイン
ssh user@server-ip
# ログを確認
cd ~/flask-app
docker-compose -f docker-compose.prod.yml logs
# コンテナの状態を確認
docker-compose -f docker-compose.prod.yml ps
ベストプラクティス
1. Secrets の管理
- パスワードは必ず GitHub Secrets に保存
-
.envファイルは.gitignoreに追加 - 定期的にパスワードをローテーション
2. テストの充実
# app/test_app.py にテストを追加
def test_add_user_success(client, mocker):
"""ユーザー追加の成功テスト"""
# データベース接続をモック
mock_conn = mocker.patch('app.get_db_connection')
response = client.post('/add', data={
'name': 'Test User',
'email': 'test@example.com'
})
assert response.status_code == 302
3. イメージのタグ戦略
tags: |
${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:latest
${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:${{ github.sha }}
${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:v1.0.0
4. キャッシュの活用
- name: Docker レイヤーキャッシュ
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
5. セキュリティスキャン
- name: Trivy によるセキュリティスキャン
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ secrets.DOCKER_USERNAME }}/flask-pipeline-app:latest
format: 'sarif'
output: 'trivy-results.sarif'
まとめ
- GitHub Actions で CI/CD パイプラインを構築
- コードを push するだけで自動テスト・ビルド・デプロイ
- Docker Hub にイメージを自動 push
- SSH 経由でサーバーに自動デプロイ
- Secrets で機密情報を安全に管理
次のステップ
- Kubernetes へのデプロイ
- AWS ECS/Fargate の利用
- Blue-Green デプロイメント
- カナリアリリース
- モニタリングとログ集約
