コンテンツにスキップ

Docker パブリッシングガイド

本番環境への EvoSpikeNet-Core Docker イメージ配布ワークフローを説明します。

目次

  1. 概要
  2. 前提条件
  3. ステージ別ワークフロー
  4. レジストリ設定
  5. イメージ署名と検証
  6. セキュリティベストプラクティス
  7. CI/CD 統合
  8. トラブルシューティング

概要

パブリッシング戦略

EvoSpikeNet-Core は段階的なセキュリティレベルで配布できます:

開発環境          ステージング環境        プリプロ環境          本番環境
    ↓                  ↓                    ↓                    ↓
[source]  →  [wheel]  →  [cython]  →  [nuitka]
完全公開     ソース除外    モジュル難読    完全保護

推奨フロー

1. ローカル開発: source モードで機能実装・テスト
   ↓
2. CI テスト: 全モード (source/wheel/cython/nuitka) を並列ビルド・テスト
   ↓
3. ステージング検証: wheel または cython でデプロイ
   ↓
4. 本番リリース: nuitka でビルド後、レジストリにプッシュ
   ↓
5. デプロイ: イメージ署名検証後、本番環境で実行

前提条件

必須ツール

# Docker (20.10+)
docker --version

# Docker Buildx(マルチプラットフォームビルド用)
docker buildx version

# crane(イメージ検査用、オプション)
which crane || echo "Not installed"

# cosign(イメージ署名用、オプション)
which cosign || echo "Not installed"

インストール例(Ubuntu 22.04):

# Docker Buildx
git clone https://github.com/docker/buildx.git
cd buildx
make build
mkdir -p ~/.docker/cli-plugins
cp ./bin/build/docker-buildx ~/.docker/cli-plugins/docker-buildx
chmod +x ~/.docker/cli-plugins/docker-buildx

# cosign
wget https://github.com/sigstore/cosign/releases/download/v2.0.0/cosign-linux-amd64
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign

レジストリアカウント

  • Azure Container Registry (ACR)
  • Docker Hub
  • GitHub Container Registry (GHCR)
  • プライベートレジストリ(自社運用)

ステージ別ワークフロー

Phase 1: ローカル開発 (source モード)

目的: 機能実装・バグ修正・デバッグ

# デフォルトで source モード
docker build -t evospikenet:dev .

# 起動して動作確認
docker run --rm -it -p 18000:8000 evospikenet:dev bash

# ホットリロード用マウント
docker run --rm -p 18000:8000 \
  -v $PWD/evospikenet:/opt/venv/lib/python3.10/site-packages/evospikenet \
  evospikenet:dev

テスト実行:

# コンテナ内でテスト実行
docker run --rm evospikenet:dev pytest tests/

# または docker-compose を使用
docker compose up -d api
docker compose exec api pytest tests/api/

Phase 2: CI テストパイプライン

目的: 全ビルドモードの自動検証(GitHub Actions の例)

.github/workflows/docker-publish.yml:

name: Docker Build & Publish

on:
  push:
    branches: [main, develop]
    tags: ['v*']
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}/evospikenet

jobs:
  build-matrix:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        mode: [source, wheel, cython, nuitka]
        include:
          - mode: source
            target: runtime
            tag_suffix: latest
          - mode: wheel
            target: runtime-wheel
            tag_suffix: wheel
          - mode: cython
            target: runtime-cython
            tag_suffix: cython
          - mode: nuitka
            target: runtime-nuitka
            tag_suffix: nuitka

    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch,suffix=-${{ matrix.tag_suffix }}
            type=semver,pattern={{version}},suffix=-${{ matrix.tag_suffix }}
            type=sha,suffix=-${{ matrix.tag_suffix }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: ./EvoSpikeNet-Core
          target: ${{ matrix.target }}
          build-args: |
            APP_IMAGE_MODE=${{ matrix.mode }}
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Test image
        run: |
          docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-${{ matrix.tag_suffix }} \
            python -c "import evospikenet.api; print('✓ Image test passed')"

ローカル CI テスト:

# すべてのモードをビルド・テスト
for mode in source wheel cython nuitka; do
  echo "Building mode: $mode"
  target="runtime"
  [[ "$mode" != "source" ]] && target="runtime-$mode"

  docker build \
    --build-arg APP_IMAGE_MODE=$mode \
    --target $target \
    -t evospikenet:test-$mode \
    . || exit 1
done

echo "All modes built successfully"

Phase 3: ステージング検証 (wheel/cython モード)

目的: 本番環境に近い環境でテスト

3.1 ステージングレジストリへプッシュ

# ステージングレジストリ設定
STAGING_REGISTRY="staging.azurecr.io"
APP_NAME="evospikenet"
VERSION="1.0.0-rc1"

# Cython モードでビルド
docker build \
  --build-arg APP_IMAGE_MODE=cython \
  --target runtime-cython \
  -t $STAGING_REGISTRY/$APP_NAME:$VERSION-cython \
  .

# ステージングレジストリにプッシュ
docker login $STAGING_REGISTRY
docker push $STAGING_REGISTRY/$APP_NAME:$VERSION-cython

3.2 ステージング環境でのテスト

docker-compose.staging.yml:

version: '3.8'

services:
  api:
    image: staging.azurecr.io/evospikenet:1.0.0-rc1-cython
    ports:
      - "18000:8000"
    environment:
      - LOG_LEVEL=INFO
      - ENVIRONMENT=staging
    volumes:
      - ./logs:/opt/venv/var/logs
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - api

テスト実行:

# ステージング環境起動
docker compose -f docker-compose.staging.yml up -d

# ヘルスチェック
curl -v http://localhost:18000/health

# パフォーマンステスト
docker run --rm --network host \
  loadimpact/k6 run \
  -e BASE_URL=http://localhost:18000 \
  tests/k6/performance.js

# ステージング環境停止
docker compose -f docker-compose.staging.yml down

Phase 4: 本番リリース (nuitka モード)

目的: 最高のセキュリティレベルで本番環境にデプロイ

4.1 本番イメージのビルド

# 本番レジストリ設定
PROD_REGISTRY="production.azurecr.io"
APP_NAME="evospikenet"
VERSION="1.0.0"

# Nuitka モードでビルド(時間がかかる)
docker build \
  --build-arg APP_IMAGE_MODE=nuitka \
  --target runtime-nuitka \
  -t $PROD_REGISTRY/$APP_NAME:$VERSION \
  -t $PROD_REGISTRY/$APP_NAME:latest-nuitka \
  .

# イメージサイズ確認
docker images | grep $PROD_REGISTRY/$APP_NAME

4.2 イメージ署名(オプション)

# cosign をインストール(未インストール時)
curl -fsSLO https://github.com/sigstore/cosign/releases/download/v2.0.0/cosign-linux-amd64
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign

# キーペアを生成(初回のみ)
cosign generate-key-pair

# イメージに署名
cosign sign --key cosign.key $PROD_REGISTRY/$APP_NAME:$VERSION

# プッシュ前に署名を検証
cosign verify --key cosign.pub $PROD_REGISTRY/$APP_NAME:$VERSION

4.3 本番レジストリへプッシュ

# 本番レジストリにログイン
docker login $PROD_REGISTRY

# イメージをプッシュ
docker push $PROD_REGISTRY/$APP_NAME:$VERSION
docker push $PROD_REGISTRY/$APP_NAME:latest-nuitka

# イメージが正常にプッシュされたか確認
curl -u $REGISTRY_USER:$REGISTRY_PASSWORD \
  https://$PROD_REGISTRY/v2/$APP_NAME/manifests/$VERSION

4.4 本番環境へのデプロイ

Kubernetes 例:

# k8s/evospikenet-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: evospikenet-api
  namespace: production
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: evospikenet-api
  template:
    metadata:
      labels:
        app: evospikenet-api
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault

      containers:
      - name: api
        image: production.azurecr.io/evospikenet:1.0.0
        imagePullPolicy: Always
        ports:
        - containerPort: 8000
          name: http

        env:
        - name: LOG_LEVEL
          value: "INFO"
        - name: ENVIRONMENT
          value: "production"
        - name: EVOSPIKENET_API_KEY
          valueFrom:
            secretKeyRef:
              name: evospikenet-secrets
              key: api-key

        resources:
          requests:
            cpu: 500m
            memory: 1Gi
          limits:
            cpu: 2000m
            memory: 2Gi

        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10

        readinessProbe:
          httpGet:
            path: /ready
            port: 8000
          initialDelaySeconds: 10
          periodSeconds: 5

        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
            - ALL

        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: var-tmp
          mountPath: /var/tmp

      volumes:
      - name: tmp
        emptyDir: {}
      - name: var-tmp
        emptyDir: {}

      imagePullSecrets:
      - name: registry-credentials

デプロイコマンド:

# シークレット作成
kubectl create secret docker-registry registry-credentials \
  --docker-server=$PROD_REGISTRY \
  --docker-username=$REGISTRY_USER \
  --docker-password=$REGISTRY_PASSWORD \
  -n production

# デプロイメント実行
kubectl apply -f k8s/evospikenet-deployment.yaml

# ロールアウト進捗確認
kubectl rollout status deployment/evospikenet-api -n production

# Pod ログ確認
kubectl logs -f deployment/evospikenet-api -n production

レジストリ設定

Azure Container Registry (ACR)

# レジストリ作成(初回のみ)
az acr create --resource-group myResourceGroup \
  --name myregistry --sku Basic

# ログイン認証情報取得
az acr credential show --name myregistry

# Docker ログイン
az acr login --name myregistry

# イメージをプッシュ
docker tag evospikenet:latest myregistry.azurecr.io/evospikenet:1.0.0
docker push myregistry.azurecr.io/evospikenet:1.0.0

Docker Hub

# ログイン
docker login

# イメージをプッシュ
docker tag evospikenet:latest myaccount/evospikenet:1.0.0
docker push myaccount/evospikenet:1.0.0

# 公開設定確認
curl -s https://hub.docker.com/v2/users/$USER/repositories | jq '.results[] | {name, is_private}'

GitHub Container Registry (GHCR)

# Personal Access Token (PAT) を生成
# GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
# スコープ: write:packages, read:packages, delete:packages

# 環境変数設定
export CR_PAT="ghp_xxxxxxxxxxxxx"
export REGISTRY=ghcr.io
export IMAGE_NAME=github.com/${{ github.repository }}/evospikenet

# ログイン
echo "$CR_PAT" | docker login $REGISTRY -u $GITHUB_ACTOR --password-stdin

# イメージをプッシュ
docker tag evospikenet:latest $REGISTRY/$(echo $IMAGE_NAME | tr '[:upper:]' '[:lower:]'):1.0.0
docker push $REGISTRY/$(echo $IMAGE_NAME | tr '[:upper:]' '[:lower:]'):1.0.0

プライベートレジストリ(オンプレミス)

# レジストリイメージ起動
docker run -d \
  -p 5000:5000 \
  --name registry \
  -v registry-data:/var/lib/registry \
  registry:2

# TLS 設定(本番推奨)
mkdir -p registry-certs
openssl req -new -newkey rsa:4096 -days 365 -nodes \
  -x509 -keyout registry-certs/domain.key \
  -out registry-certs/domain.crt

docker run -d \
  -p 5000:5000 \
  --name registry \
  -v registry-data:/var/lib/registry \
  -v registry-certs:/certs \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  registry:2

# イメージをプッシュ
docker tag evospikenet:latest localhost:5000/evospikenet:1.0.0
docker push localhost:5000/evospikenet:1.0.0

イメージ署名と検証

イメージ署名(cosign + Sigstore)

# 初回:キーペアを生成
cosign generate-key-pair

# イメージに署名
cosign sign --key cosign.key $REGISTRY/$IMAGE_NAME:$VERSION

# 署名を検証
cosign verify --key cosign.pub $REGISTRY/$IMAGE_NAME:$VERSION

SBOM(Software Bill of Materials)の生成

# syft でSBOM を生成
syft $REGISTRY/$IMAGE_NAME:$VERSION > sbom.spdx.json

# SBOM をイメージに添付
cosign attach sbom --sbom sbom.spdx.json $REGISTRY/$IMAGE_NAME:$VERSION

# SBOM を検索して表示
cosign download sbom $REGISTRY/$IMAGE_NAME:$VERSION

スキャン(脆弱性チェック)

# Trivy でスキャン
trivy image $REGISTRY/$IMAGE_NAME:$VERSION

# 重大度別フィルタ
trivy image --severity HIGH,CRITICAL $REGISTRY/$IMAGE_NAME:$VERSION

# JSON 形式で出力
trivy image -f json -o scan-report.json $REGISTRY/$IMAGE_NAME:$VERSION

セキュリティベストプラクティス

1. イメージ署名と検証の自動化

# GitHub Actions ワークフロー
- name: Sign image with Cosign
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'
  uses: sigstore/cosign-installer@v3
  with:
    cosign-release: 'v2.0.0'

- name: Run Cosign
  run: |
    cosign sign --key cosign.key ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

2. イメージスキャンの自動化

# スキャン・アクション
- name: Run Trivy scan
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
    format: 'sarif'
    output: 'trivy-results.sarif'

- name: Upload Trivy results
  uses: github/codeql-action/upload-sarif@v2
  with:
    sarif_file: 'trivy-results.sarif'

3. イメージの不変性

# イメージハッシュでプル(タグではなく)
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $REGISTRY/$IMAGE_NAME:$VERSION)
docker pull $DIGEST

4. ネットワークセキュリティ

# Docker ログイン用の秘密管理(GitHub Secrets)
- name: Log in to Registry
  uses: docker/login-action@v3
  with:
    registry: ${{ secrets.REGISTRY_URL }}
    username: ${{ secrets.REGISTRY_USERNAME }}
    password: ${{ secrets.REGISTRY_PASSWORD }}

5. ローカルイメージ管理

# ビルド後のイメージを自動削除(タグ付け後)
docker build ... -t $REGISTRY/$IMAGE_NAME:$VERSION .
docker push $REGISTRY/$IMAGE_NAME:$VERSION
docker rmi $REGISTRY/$IMAGE_NAME:$VERSION

# ローカルストレージをクリーンアップ
docker builder prune -a
docker image prune -a

CI/CD 統合

GitHub Actions 統合ワークフロー例

.github/workflows/docker-publish-prod.yml:

name: Build & Publish Production Image

on:
  push:
    tags: ['v*']

env:
  REGISTRY: production.azurecr.io
  IMAGE_NAME: evospikenet

jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
    - uses: actions/checkout@v4

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3

    - name: Log in to Production Registry
      uses: docker/login-action@v3
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ secrets.PROD_REGISTRY_USERNAME }}
        password: ${{ secrets.PROD_REGISTRY_PASSWORD }}

    - name: Extract version from tag
      id: version
      run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

    - name: Build and push Nuitka image
      uses: docker/build-push-action@v5
      with:
        context: ./EvoSpikeNet-Core
        target: runtime-nuitka
        build-args: APP_IMAGE_MODE=nuitka
        push: true
        tags: |
          ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
          ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-nuitka

    - name: Scan image with Trivy
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
        format: 'sarif'
        output: 'trivy-results.sarif'

    - name: Upload Trivy results to GitHub
      uses: github/codeql-action/upload-sarif@v2
      with:
        sarif_file: 'trivy-results.sarif'

    - name: Create Release Notes
      run: |
        cat > RELEASE_NOTES.md <<EOF
        # Release: ${{ steps.version.outputs.VERSION }}

        Image: \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}\`
        Build Mode: nuitka (maximum source protection)

        Run: \`docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}\`
        EOF

    - name: Upload Release
      uses: softprops/action-gh-release@v1
      with:
        files: RELEASE_NOTES.md

トラブルシューティング

イメージプッシュ失敗

# エラー: denied: authorization failed
# → レジストリ認証確認
docker login $REGISTRY
docker push $IMAGE

# エラー: invalid reference format
# → イメージ名の形式確認
docker tag local-image:latest registry.com/namespace/image:1.0.0

# エラー: blob upload unknown
# → ネットワーク接続確認、レジストリのディスク容量確認
docker push --verbose $IMAGE

イメージ署名の問題

# cosign キーが見つからない
export COSIGN_KEY_LOCATION=~/.cosign/cosign.key
cosign sign $IMAGE

# 署名検証失敗
cosign verify --key cosign.pub $IMAGE
# → cosign.pub が正しい公開鍵か確認

Kubernetes デプロイメント失敗

# イメージプルエラー
kubectl describe pod <pod-name> -n production
# → imagePullSecrets の確認
# → レジストリ認証情報の確認

# シークレット確認
kubectl get secrets -n production
kubectl describe secret registry-credentials -n production

関連ドキュメント