第5章:ストレージとボリューム管理
本章の意義と学習目標
なぜイメージ作成を深く理解する必要があるのか
「コンテナを使う」から「コンテナを作る」へのステップは、以下の価値をもたらします:
- ビルド時間の短縮: 最適化により、デプロイサイクルを10倍高速化
- セキュリティ向上: 脆弱性を最小限に抑えた安全なイメージ作成
- 運用コスト削減: イメージサイズ削減により、ストレージとネットワークコストを大幅削減
- 保守性向上: 再現可能で理解しやすいイメージ構成
本章では、「動くイメージ」から「本番環境に適したイメージ」を作成する技術を習得します。
5.1 Containerfile/Dockerfile詳解
なぜContainerfileの各命令を理解することが重要か
各命令は単なる手順ではなく、パフォーマンス、セキュリティ、保守性に直接影響します:
5.1.1 基本構造と命令
基本的なContainerfileとその最適化ポイント
# ベースイメージ - なぜ選択が重要か
FROM alpine:3.18
# Alpine選択の理由:
# - サイズ: 5MB(Ubuntu: 80MB以上)
# - セキュリティ: 最小限のパッケージ = 攻撃面の最小化
# - 起動速度: 小さいイメージ = 高速なpull/起動
# メタデータ - なぜ重要か
LABEL maintainer="admin@example.com"
LABEL version="1.0"
LABEL description="Sample application"
# 効果:
# - 運用時の問い合わせ先明確化
# - 自動化ツールでのフィルタリング
# - コンプライアンス要件の充足
# 引数とビルド時変数 - 柔軟性の確保
ARG BUILD_DATE
ARG VERSION=latest
# 活用シーン:
# - CI/CDでのバージョン自動注入
# - 環境別のビルド設定
# 環境変数 - 実行時設定の外部化
ENV APP_HOME=/app \
NODE_ENV=production
# ベストプラクティス:
# - 機密情報は含めない(ビルド時に埋め込まれる)
# - 実行時に上書き可能な設定のみ
# 作業ディレクトリ - なぜ明示的に設定するか
WORKDIR $APP_HOME
# 効果:
# - 相対パスの基準点明確化
# - デバッグ時の作業効率向上
# パッケージインストール - 最適化の要
RUN apk add --no-cache \
nodejs \
npm \
curl
# --no-cacheの重要性:
# - パッケージインデックスをイメージに含めない
# - イメージサイズを数十MB削減
# ファイルコピー - レイヤーキャッシュの活用
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# この順序の理由:
# - package.jsonが変更されない限り、npm ciはキャッシュから実行
# - ソースコード変更時も依存関係インストールをスキップ
# - ビルド時間を大幅短縮(5分→30秒)
# ポート公開 - ドキュメンテーションとしての役割
EXPOSE 3000
# 注意:実際のポート開放ではなく、使用ポートの明示
# ヘルスチェック - 本番運用の必須要件
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# 各パラメータの意味:
# - interval: チェック間隔(頻繁すぎるとオーバーヘッド)
# - timeout: 応答待ち時間(ネットワーク遅延を考慮)
# - start-period: 起動猶予時間(アプリケーション初期化を待つ)
# - retries: 失敗とみなすまでの試行回数
# 実行ユーザー - セキュリティの基本
USER node
# rootで実行しない理由:
# - コンテナ脱出時の被害限定
# - 最小権限の原則
# エントリーポイントとコマンド - 柔軟な実行制御
ENTRYPOINT ["node"]
CMD ["server.js"]
# ENTRYPOINTとCMDの使い分け:
# - ENTRYPOINT: 変更されない実行コマンド
# - CMD: デフォルト引数(上書き可能)
5.1.2 高度な命令と最適化
マルチステージビルドの革新的価値
# なぜマルチステージビルドが必要か
# 従来の問題:ビルドツールが本番イメージに含まれる
# - セキュリティリスク(コンパイラ、デバッグツール)
# - イメージサイズ肥大化(数GB)
# - 攻撃面の増大
# Stage 1: Dependencies - 依存関係の分離
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# このステージの成果物:本番用node_modules
# Stage 2: Build - ビルドプロセスの分離
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci # 開発依存関係も含めてインストール
COPY . .
RUN npm run build
# このステージの成果物:ビルド済みアプリケーション
# Stage 3: Runtime - 最小限の実行環境
FROM node:18-alpine AS runtime
RUN apk add --no-cache dumb-init # PID 1問題の解決
WORKDIR /app
# セキュリティ: 非rootユーザー作成
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# 前のステージから必要なものだけコピー
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
USER nodejs
EXPOSE 3000
# dumb-initを使う理由:
# - 適切なシグナルハンドリング
# - ゾンビプロセスの防止
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/index.js"]
# 結果:
# - 開発ツールなし = セキュア
# - 最小限の実行環境 = 50MB(元の1/20)
# - 高速な配布 = デプロイ時間短縮
ビルドキャッシュ最適化
# 悪い例:キャッシュが効きにくい
COPY . .
RUN npm install
# 良い例:依存関係を先にコピー
COPY package*.json ./
RUN npm ci
COPY . .
5.1.3 条件付きビルド
# ビルド引数による条件分岐
ARG ENVIRONMENT=production
# 開発環境用の追加パッケージ
RUN if [ "$ENVIRONMENT" = "development" ]; then \
apk add --no-cache \
git \
vim \
curl; \
fi
# 環境別設定ファイル
COPY config.$ENVIRONMENT.json /app/config.json
5.2 効率的なイメージ作成
5.2.1 レイヤー最適化
レイヤーの理解
# レイヤー構造の可視化
podman history --no-trunc nginx:alpine
# レイヤーサイズの確認
podman images --tree
最適化テクニック
# 悪い例:複数レイヤー
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y nginx
RUN apt-get clean
# 良い例:単一レイヤー
RUN apt-get update && \
apt-get install -y \
curl \
nginx && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
5.2.2 サイズ削減技術
ベースイメージの選択
# サイズ比較
# debian:11 124MB
# ubuntu:22.04 77MB
# alpine:3.18 7MB
# distroless 2MB
# Distrolessイメージの使用
FROM gcr.io/distroless/nodejs18-debian11
COPY --from=builder /app/dist /app
WORKDIR /app
CMD ["index.js"]
不要ファイルの除外
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
coverage/
.nyc_output/
*.test.js
5.2.3 セキュリティベストプラクティス
セキュアなイメージ作成
# 1. 最小権限の原則
FROM alpine:3.18
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 2. 機密情報の扱い(ビルド時シークレット)
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci --only=production
# 3. イメージスキャン用メタデータ
LABEL security.scan="required"
LABEL security.vulnerabilities="none"
# 4. 読み取り専用ファイルシステム
RUN chmod -R a-w /app
USER appuser
5.3 Buildahによる高度なイメージ作成
5.3.1 Buildahの基本
# スクラッチからのイメージ作成
buildah from scratch
container=$(buildah from scratch)
# マウントとファイル操作
mountpoint=$(buildah mount $container)
cp -r ./app/* $mountpoint/
buildah umount $container
# 設定
buildah config --port 8080 $container
buildah config --entrypoint '["/app/server"]' $container
# コミット
buildah commit $container myapp:latest
5.3.2 スクリプトによるイメージ作成
#!/bin/bash
# build-image.sh
set -e
# ベースコンテナ作成
container=$(buildah from alpine:3.18)
mountpoint=$(buildah mount $container)
# パッケージインストール
buildah run $container apk add --no-cache \
python3 \
py3-pip
# アプリケーションコピー
cp -r ./src $mountpoint/app
# Pythonパッケージインストール
buildah run $container pip3 install -r /app/requirements.txt
# 設定
buildah config --workingdir /app $container
buildah config --cmd "python3 server.py" $container
buildah config --port 5000 $container
# クリーンアップとコミット
buildah unmount $container
buildah commit $container myapp:$(date +%Y%m%d)
5.4 イメージレジストリ
5.4.1 プライベートレジストリ構築
基本的なレジストリ
# レジストリコンテナ起動
podman run -d \
--name registry \
-p 5000:5000 \
-v registry-data:/var/lib/registry \
docker.io/registry:2
# HTTPS対応レジストリ
podman run -d \
--name secure-registry \
-p 443:443 \
-v ./certs:/certs \
-v registry-data:/var/lib/registry \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
registry:2
認証付きレジストリ
# htpasswd作成
htpasswd -Bbn testuser testpassword > htpasswd
# 認証付きレジストリ
podman run -d \
--name auth-registry \
-p 5000:5000 \
-v ./htpasswd:/auth/htpasswd \
-v registry-data:/var/lib/registry \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
registry:2
5.4.2 レジストリ操作
イメージのプッシュ/プル
# タグ付け
podman tag myapp:latest localhost:5000/myapp:latest
# ログイン
podman login localhost:5000
# プッシュ
podman push localhost:5000/myapp:latest
# プル
podman pull localhost:5000/myapp:latest
# レジストリ内容確認
curl -X GET http://localhost:5000/v2/_catalog
curl -X GET http://localhost:5000/v2/myapp/tags/list
レジストリミラーリング
# Skopeoでのミラーリング
skopeo sync \
--src docker \
--dest docker \
docker.io/library/nginx:latest \
localhost:5000
# 一括同期
cat > sync.yaml << EOF
docker.io:
images:
library/nginx:
- "latest"
- "1.21"
library/alpine:
- "3.18"
EOF
skopeo sync --src yaml --dest docker sync.yaml localhost:5000
5.5 イメージの署名と検証
5.5.1 GPG署名
# GPGキー生成
gpg --gen-key
# 署名ポリシー設定
cat > /etc/containers/policy.json << EOF
{
"default": [{"type": "insecureAcceptAnything"}],
"transports": {
"docker": {
"localhost:5000": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "/etc/pki/rpm-gpg/mykey.pub"
}
]
}
}
}
EOF
# イメージ署名
podman push --sign-by myemail@example.com localhost:5000/myapp:latest
演習問題
- マルチステージビルドを使用して、Goアプリケーションの最小イメージを作成してください
- Buildahスクリプトでカスタムイメージを作成してください
- プライベートレジストリを構築し、署名付きイメージをプッシュしてください