たねやつの木

Photographs, Keyboards and Programming

【CI/CD勉強編-1】なぜCI/CD?開発環境(WSL)でFastAPIアプリをDockerで動かす

こんにちは、たねやつです。

新しい技術を学ぶとき、チュートリアルをなぞるだけだとモチベーションが続かないことがありますよね。そこで今回は、「手元のRaspberry Piに自作アプリを自動でデプロイする」という具体的なゴールを設定して、モダンな開発手法であるCI/CDを学んでいくシリーズを始めたいと思います。

最終的には、コードをGitLabにプッシュするだけで、テスト、Dockerイメージのビルド、そしてRaspberry Pi上のKubernetesへのデプロイが全自動で行われる環境を目指します。

第1回は、その第一歩として、なぜCI/CDが必要なのかに触れつつ、開発環境であるWSL上でDockerを使ってFastAPIとPostgreSQLのアプリケーションを立ち上げるところまでを解説します。

この記事でできること

  • CI/CDの基本的な概念とメリットがわかる。
  • WSL(Windows Subsystem for Linux)にDocker CEをインストールし、開発環境を構築できる。
  • docker composeを使い、FastAPIとPostgreSQLを連携させたWebアプリケーションを起動できる。

事前に必要なもの

  • Windows PC

    • WSL2がインストールおよび設定済みであること。(本記事ではUbuntuを想定)
  • テキストエディタ

    • VSCodeなど、コードが書けるエディタ。

CI/CDとは? なぜ必要なのか?

CI/CDは、継続的インテグレーション (Continuous Integration)継続的デリバリー/デプロイ (Continuous Delivery/Deployment) の略です。

  • CI (継続的インテグレーション): 開発者が書いたコードを頻繁にメインのリポジトリにマージし、そのたびに自動でビルドやテストを実行する仕組みです。これにより、問題を早期に発見できます。

  • CD (継続的デリバリー/デプロイ): CIを通過したコードを、自動的に本番環境(またはステージング環境)へリリースできるようにする仕組みです。手作業によるミスをなくし、迅速に新機能をユーザーに届けることができます。

これを導入することで、「手元では動いたのに、サーバーに上げたら動かない…」「リリース作業が毎回緊張する…」といった開発のよくある悩みから解放され、より創造的な作業に集中できるようになります。

アプリケーションの構成

今回CI/CDパイプラインに乗せていくのは、シンプルなAPIサーバーです。

  • バックエンド: FastAPI (PythonのWebフレームワーク)
  • データベース: PostgreSQL

この2つをDockerコンテナとして起動し、連携させます。

開発環境の構築手順

それでは、実際にWSL上で開発環境を構築していきましょう。

1. WSLにDocker CEをインストール

以前の記事を参考に

3. 作業ディレクトリの作成

まず、作業用のディレクトリ(例えば cicd-learning)を作成し、その中に移動します。

mkdir cicd-learning
cd cicd-learning

4. FastAPIアプリケーションの準備

srcというディレクトリを作成し、その中にFastAPIのコードと必要なライブラリを記述したファイルを作成します。

mkdir src
  • src/requirements.txt: 必要なPythonライブラリを列挙します。
fastapi
uvicorn[standard]
sqlalchemy
psycopg2-binary
  • src/main.py: APIサーバー本体のコードです。
import os
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.exc import OperationalError
import time

# 環境変数からデータベースURLを取得
DATABASE_URL = os.getenv("DATABASE_URL")

# データベースエンジンを作成
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

app = FastAPI()

# DB接続を試みる関数
def try_db_connection():
    for i in range(10): # 10回リトライ
        try:
            with engine.connect() as connection:
                connection.execute(text("SELECT 1"))
            print("Database connection successful.")
            return
        except OperationalError as e:
            print(f"Database connection failed. Retrying... ({i+1}/10)")
            time.sleep(3)
    raise Exception("Could not connect to the database.")

# アプリケーション起動時にDB接続を確認
@app.on_event("startup")
def on_startup():
    try_db_connection()

# DBセッションを取得するためのDependency
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/")
def read_root():
    return {"message": "Hello from FastAPI with PostgreSQL!"}

@app.get("/db-check")
def db_check(db: Session = Depends(get_db)):
    try:
        result = db.execute(text("SELECT 1")).scalar()
        return {"db_status": "ok", "result": result}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/db-version")
def db_version(db: Session = Depends(get_db)):
    try:
        result = db.execute(text("SELECT version()")).scalar()
        return {"db_version": result}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

5. Dockerfileの作成

次に、FastAPIアプリケーションをコンテナ化するための設計図であるDockerfileを作成します。

  • Dockerfile:
# Pythonの公式イメージをベースにする
FROM python:3.9-slim

# 作業ディレクトリを設定
WORKDIR /app

# 必要なライブラリをインストール
COPY src/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# アプリケーションのソースコードをコピー
COPY ./src /app/src

# コンテナ起動時に実行するコマンド
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

6. docker-compose.ymlの作成

apiサービスとdbサービスをまとめて起動・連携させるために、docker-compose.ymlを作成します。

  • docker-compose.yml:
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - ./src:/app/src
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/mydatabase
    depends_on:
      db:
        condition: service_healthy
    command: uvicorn src.main:app --host 0.0.0.0 --port 8000 --reload

  db:
    image: postgres:15
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydatabase
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydatabase"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:
ポイント: `api`サービスの`depends_on`で`condition: service_healthy`を指定しています。これにより、`db`コンテナのヘルスチェックが通って、データベースが完全に準備できてから`api`コンテナが起動するようになり、起動時の接続エラーを防ぎます。

7. コンテナの起動

全てのファイルが準備できたら、以下のコマンドでコンテナを起動します。docker-composeではなくdocker compose(ハイフンなし)である点に注意してください。

docker compose up --build

初回はDockerイメージのビルドに少し時間がかかります。コンソールにDatabase connection successful.Application startup complete.といったログが表示されれば成功です。

8. 動作確認

ブラウザで http://localhost:8000 にアクセスしてみてください。 {"message":"Hello from FastAPI with PostgreSQL!"} と表示されれば、APIサーバーは正常に動いています。

次に、http://localhost:8000/db-check にアクセスします。 {"db_status":"ok","result":1} と表示されれば、PostgreSQLとの接続も成功しています。

最後に、新しいエンドポイント http://localhost:8000/db-version にアクセスします。 {"db_version":"PostgreSQL 15..."} のように、PostgreSQLのバージョン情報が表示されれば完璧です。

最後に

今回は、CI/CD学習の第一歩として、WSL上にDocker CEを直接インストールし、FastAPI+PostgreSQLの実行環境を構築しました。docker composeを使うことで、複数のサービスが連携するアプリケーションも簡単に立ち上げることができました。

しかし、ここまではまだ手作業での実行です。次回は、いよいよGitLab CI/CDを導入し、コードをプッシュしたら自動でDockerイメージがビルドされる、CIパイプラインの構築に挑戦します。お楽しみに!

次の記事