第9章:サーバーレスとコンテナサービス
9.1 サーバーレスコンピューティングの概念とメリット
サーバーレスという革命的パラダイム
サーバーレスコンピューティングは、その名前が示すような「サーバーがない」状態ではありません。サーバーの存在を開発者から完全に抽象化し、ビジネスロジックの実装に集中できる環境を提供する、インフラストラクチャ管理の革命的なアプローチです。
従来のアプローチからの解放
インフラストラクチャ管理の負担
従来のアプリケーション開発では、開発者は以下のような運用タスクに多大な時間を費やしていました。
`yaml 従来の責任範囲: インフラ層: - サーバーのプロビジョニング - OSのパッチ適用 - セキュリティアップデート - キャパシティプランニング
スケーリング層: - 負荷分散の設計 - オートスケーリングの設定 - 可用性の確保 - 障害時のフェイルオーバー
運用層: - 監視システムの構築 - ログ収集と分析 - バックアップとリカバリ - コスト管理 `
サーバーレスは、これらすべての責任をクラウドプロバイダーに委譲し、開発者を運用の複雑性から解放します。
イベント駆動アーキテクチャの本質
リアクティブシステムの実現
サーバーレスの真の価値は、イベント駆動型の設計思想にあります。システムは外部イベントに反応し、必要な時にのみ処理を実行します。
`python
Lambda関数の例:S3イベントに反応する画像処理
import json import boto3 from PIL import Image import io
s3_client = boto3.client(‘s3’)
def lambda_handler(event, context): “”” S3にアップロードされた画像を自動的にリサイズ “”” # イベントからバケット名とオブジェクトキーを取得 for record in event[‘Records’]: bucket = record[‘s3’][‘bucket’][‘name’] key = record[‘s3’][‘object’][‘key’]
# 元画像をダウンロード
response = s3_client.get_object(Bucket=bucket, Key=key)
image_content = response['Body'].read()
# 画像処理
with Image.open(io.BytesIO(image_content)) as img:
# サムネイル作成(150x150)
thumbnail = img.copy()
thumbnail.thumbnail((150, 150))
# WebP形式で保存(高圧縮)
output_buffer = io.BytesIO()
thumbnail.save(output_buffer, format='WEBP', quality=85)
output_buffer.seek(0)
# 処理済み画像をS3に保存
thumbnail_key = f"thumbnails/{key}"
s3_client.put_object(
Bucket=bucket,
Key=thumbnail_key,
Body=output_buffer,
ContentType='image/webp'
)
return {
'statusCode': 200,
'body': json.dumps('Image processing completed')
} `
多様なイベントソース
`yaml イベントソースの分類: HTTPリクエスト: - API Gateway(REST/GraphQL) - Application Load Balancer - CloudFront(Lambda@Edge)
メッセージングサービス: - SQS(Simple Queue Service) - SNS(Simple Notification Service) - EventBridge(カスタムイベント) - Kinesis(ストリーミングデータ)
ストレージイベント: - S3(オブジェクト作成/削除) - DynamoDB Streams(データ変更) - EFS(ファイルシステムイベント)
スケジュールイベント: - CloudWatch Events(cron式) - EventBridge Scheduler
その他: - IoT Core(デバイスメッセージ) - Cognito(認証イベント) - Step Functions(ワークフロー) `
Function as a Service (FaaS) の実行モデル
コールドスタートの理解と最適化
コールドスタートは、サーバーレスの特性上避けられない課題ですが、適切な対策により影響を最小化できます。
`python
コールドスタート最適化のテクニック
1. グローバル変数で接続を再利用
import os import pymongo
コールドスタート時のみ実行される
mongodb_client = None
def get_db_connection(): global mongodb_client if mongodb_client is None: mongodb_client = pymongo.MongoClient( os.environ[‘MONGODB_URI’], maxPoolSize=1 # Lambda環境では接続プールを最小に ) return mongodb_client
def lambda_handler(event, context): # ウォームスタート時は既存の接続を再利用 db = get_db_connection()
# ビジネスロジック
result = db.mydb.mycollection.find_one({"_id": event['id']})
return {
'statusCode': 200,
'body': json.dumps(result, default=str)
}
2. 最小限の依存関係
requirements.txt を最適化し、必要最小限のライブラリのみインストール
3. レイヤーの活用
共通ライブラリをLambda Layerとして分離
`
実行時間とメモリの最適化
`yaml Lambda設定の最適化戦略: メモリ割り当て: - 128MB〜10,240MB(10GB)の範囲 - メモリに比例してCPU性能も向上 - コスト vs パフォーマンスのバランス
タイムアウト設定: - 最大15分(900秒) - 適切なタイムアウトで無駄なコストを防止 - 非同期処理への分割を検討
同時実行数: - デフォルト: 1,000 - 予約同時実行数の設定 - スロットリング対策 `
経済モデルの革新
真の従量課金の実現
`python
コスト計算の例
def calculate_lambda_cost(memory_mb, duration_ms, requests): “”” Lambda実行コストの計算(東京リージョン) “”” # 料金設定 price_per_gb_second = 0.0000166667 price_per_request = 0.0000002 free_tier_gb_seconds = 400000 free_tier_requests = 1000000
# GB-秒の計算
gb_seconds = (memory_mb / 1024) * (duration_ms / 1000) * requests
billable_gb_seconds = max(0, gb_seconds - free_tier_gb_seconds)
# リクエスト数の計算
billable_requests = max(0, requests - free_tier_requests)
# 総コスト
compute_cost = billable_gb_seconds * price_per_gb_second
request_cost = billable_requests * price_per_request
total_cost = compute_cost + request_cost
return {
'compute_cost': compute_cost,
'request_cost': request_cost,
'total_cost': total_cost,
'cost_per_request': total_cost / requests if requests > 0 else 0
}
使用例:月間100万リクエスト、512MBメモリ、平均200ms
monthly_cost = calculate_lambda_cost( memory_mb=512, duration_ms=200, requests=1000000 ) print(f”月間コスト: ${monthly_cost[‘total_cost’]:.2f}”) `
サーバーレスの適用パターン
理想的なユースケース
`yaml
-
イベント処理パターン: 画像/動画処理: trigger: S3アップロード process: リサイズ、フォーマット変換、メタデータ抽出 output: 処理済みファイルをS3に保存
データ変換ETL: trigger: 新規ファイル到着 process: 検証、変換、集計 output: データウェアハウスへロード
通知システム: trigger: アプリケーションイベント process: 通知内容の生成 output: Email/SMS/Push通知の送信
- APIバックエンドパターン:
RESTful API:
- CRUD操作
- 認証・認可
- レート制限
GraphQL:
- スキーマ駆動開発
- リゾルバーの実装
- サブスクリプション
-
定期処理パターン: バックアップ: schedule: “0 2 * * *” # 毎日AM2:00 process: DBスナップショット作成
レポート生成: schedule: “0 9 * * MON” # 毎週月曜AM9:00 process: 週次レポート作成・配信 `
アンチパターンの認識
`yaml サーバーレスに適さないケース: 長時間実行: - 15分を超える処理 - 解決策: Step Functions、Batch、ECS
高頻度・常時実行: - 秒間数千リクエスト以上 - 解決策: コンテナ、EC2
ステートフル処理: - WebSocketの長時間接続 - 解決策: ECS、App Runner
超低レイテンシ要求: - 10ms以下の応答時間 - 解決策: EC2、Lambda@Edge `
サーバーレスアーキテクチャの設計パターン
マイクロサービス分解
`python
API Gatewayと複数Lambda関数による構成
serverless.yml (Serverless Framework)
service: ecommerce-api
provider: name: aws runtime: python3.9 region: ap-northeast-1
environment: DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: “arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}”
functions: # 商品管理API createProduct: handler: products.create events: - http: path: products method: post cors: true authorizer: aws_iam
getProduct: handler: products.get events: - http: path: products/{id} method: get cors: true
listProducts: handler: products.list events: - http: path: products method: get cors: true
# 注文管理API createOrder: handler: orders.create events: - http: path: orders method: post cors: true authorizer: aws_iam
processPayment: handler: payments.process events: - sqs: arn: Fn::GetAtt: - PaymentQueue - Arn batchSize: 10
resources: Resources: ProductsTable: Type: AWS::DynamoDB::Table Properties: TableName: ${self:provider.environment.DYNAMODB_TABLE} AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5
PaymentQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: ${self:service}-payment-queue
VisibilityTimeout: 60 `
9.2 コンテナ技術の基礎とDocker
コンテナ化がもたらす本質的価値
コンテナ技術は、「開発環境では動くのに本番では動かない」という長年の課題を根本的に解決しました。アプリケーションとその依存関係を一つのポータブルなユニットにパッケージ化することで、環境間の一貫性を保証します。
仮想化技術の進化と比較
ハードウェア仮想化からOS仮想化へ
` 仮想マシン(VM)のアーキテクチャ: ┌─────────────┬─────────────┬─────────────┐ │ App A │ App B │ App C │ ├─────────────┼─────────────┼─────────────┤ │ Guest OS │ Guest OS │ Guest OS │ ├─────────────┴─────────────┴─────────────┤ │ Hypervisor │ ├─────────────────────────────────────────┤ │ Host OS │ ├─────────────────────────────────────────┤ │ Hardware │ └─────────────────────────────────────────┘
コンテナのアーキテクチャ: ┌─────────────┬─────────────┬─────────────┐ │ App A │ App B │ App C │ ├─────────────┼─────────────┼─────────────┤ │ Container A │ Container B │ Container C │ ├─────────────┴─────────────┴─────────────┤ │ Container Runtime │ ├─────────────────────────────────────────┤ │ Host OS │ ├─────────────────────────────────────────┤ │ Hardware │ └─────────────────────────────────────────┘ `
性能とリソース効率の比較
`python
リソース使用量の比較
comparison = { “仮想マシン”: { “起動時間”: “30-60秒”, “メモリオーバーヘッド”: “~1GB/インスタンス”, “ディスク容量”: “~10GB/インスタンス”, “CPU効率”: “80-90%”, “同時実行可能数”: “10-20(標準的なホスト)” }, “コンテナ”: { “起動時間”: “1-2秒”, “メモリオーバーヘッド”: “~10MB/インスタンス”, “ディスク容量”: “~100MB/インスタンス”, “CPU効率”: “95-98%”, “同時実行可能数”: “100-1000(標準的なホスト)” } } `
Dockerの基本概念と実装
レイヤー構造の深い理解
Dockerイメージのレイヤー構造は、効率的なストレージとネットワーク転送を実現します。
`dockerfile
最適化されたDockerfile
Stage 1: ビルド環境
FROM node:16-alpine AS builder
依存関係のキャッシュ効率を上げる
WORKDIR /app COPY package*.json ./ RUN npm ci –only=production
アプリケーションコードをコピー
COPY . . RUN npm run build
Stage 2: 実行環境(マルチステージビルド)
FROM node:16-alpine
セキュリティ: 非rootユーザーの作成
RUN addgroup -g 1001 -S nodejs RUN adduser -S nodejs -u 1001
WORKDIR /app
必要最小限のファイルのみコピー
COPY –from=builder –chown=nodejs:nodejs /app/dist ./dist COPY –from=builder –chown=nodejs:nodejs /app/node_modules ./node_modules COPY –from=builder –chown=nodejs:nodejs /app/package*.json ./
セキュリティ設定
USER nodejs EXPOSE 3000
ヘルスチェックの定義
HEALTHCHECK –interval=30s –timeout=3s –start-period=5s –retries=3
CMD node healthcheck.js
PID 1問題を回避するためtiniを使用
RUN apk add –no-cache tini ENTRYPOINT [“/sbin/tini”, “–”]
CMD [“node”, “dist/server.js”] `
ビルドキャッシュの最適化
`dockerfile
キャッシュを効果的に活用する順序
変更頻度: 低 → 高
1. ベースイメージ(めったに変更されない)
FROM python:3.9-slim
2. システムパッケージ(たまに変更)
RUN apt-get update && apt-get install -y
gcc
&& rm -rf /var/lib/apt/lists/*
3. Pythonパッケージ(時々変更)
COPY requirements.txt . RUN pip install –no-cache-dir -r requirements.txt
4. アプリケーションコード(頻繁に変更)
COPY . /app WORKDIR /app
CMD [“python”, “app.py”] `
コンテナネットワーキングの詳細
ネットワークドライバーの選択と設計
`yaml
docker-compose.yml でのネットワーク設計
version: ‘3.8’
networks: frontend: driver: bridge ipam: config: - subnet: 172.20.0.0/24
backend: driver: bridge internal: true # 外部アクセス不可 ipam: config: - subnet: 172.21.0.0/24
monitoring: driver: bridge ipam: config: - subnet: 172.22.0.0/24
services: nginx: image: nginx:alpine networks: - frontend ports: - “80:80” - “443:443” depends_on: - app
app: build: ./app networks: - frontend - backend environment: - DB_HOST=postgres - REDIS_HOST=redis deploy: replicas: 3
postgres: image: postgres:13 networks: - backend volumes: - postgres_data:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD_FILE=/run/secrets/db_password secrets: - db_password
redis: image: redis:6-alpine networks: - backend command: redis-server –requirepass ${REDIS_PASSWORD}
prometheus: image: prom/prometheus networks: - monitoring - frontend # アプリケーションメトリクス収集用 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml
volumes: postgres_data: driver: local
secrets: db_password: file: ./secrets/db_password.txt `
本番環境でのコンテナ運用
ログ管理戦略
`python
Fluentdを使用した統合ログ管理
fluent.conf
@type forward port 24224 bind 0.0.0.0 </source>
Dockerコンテナログの収集
@type tail path /var/lib/docker/containers//-json.log pos_file /var/log/fluentd-docker.pos tag docker.* format json time_key time time_format %Y-%m-%dT%H:%M:%S.%NZ </source>
ログの構造化と濃縮
<filter docker.**> @type parser key_name log
</filter>
メタデータの追加
<filter docker.**> @type record_transformer
</filter>
出力先の設定
<match docker.**> @type elasticsearch host elasticsearch port 9200 logstash_format true logstash_prefix docker
</match> `
セキュリティスキャンの自動化
`yaml
CI/CDパイプラインでのセキュリティチェック
.gitlab-ci.yml
stages:
- build
- scan
- test
- deploy
variables: DOCKER_DRIVER: overlay2 IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
build: stage: build script: - docker build -t $IMAGE_TAG . - docker push $IMAGE_TAG
security_scan: stage: scan image: aquasec/trivy:latest script: # 脆弱性スキャン - trivy image –severity HIGH,CRITICAL –exit-code 1 $IMAGE_TAG # ライセンススキャン - trivy image –license-scan –exit-code 1 $IMAGE_TAG allow_failure: false
dockerfile_lint: stage: scan image: hadolint/hadolint:latest script: - hadolint Dockerfile
secrets_scan: stage: scan image: trufflesecurity/trufflehog:latest script: - trufflehog docker –image=$IMAGE_TAG `
9.3 コンテナオーケストレーション(ECS, EKS, GKE, AKS)
オーケストレーションの必然性
単一のコンテナ管理は簡単ですが、数百、数千のコンテナを本番環境で運用するには、高度な管理システムが必要です。
オーケストレーターが解決する課題
`yaml コンテナ管理の複雑性: 配置とスケジューリング: - 適切なホストの選択 - リソース制約の考慮 - アフィニティ/アンチアフィニティ
可用性の確保: - 自動的な再起動 - ヘルスチェック - ローリングアップデート - ロールバック
スケーリング: - 水平スケーリング - 垂直スケーリング - オートスケーリング
ネットワーキング: - サービスディスカバリ - ロードバランシング - サービスメッシュ
状態管理: - 永続ボリューム - ConfigMap/Secrets - StatefulSets `
Kubernetes:デファクトスタンダード
Kubernetesのアーキテクチャ
`yaml
Kubernetes デプロイメント例
apiVersion: apps/v1 kind: Deployment metadata: name: web-app namespace: production labels: app: web-app version: v1.0.0 spec: replicas: 3 revisionHistoryLimit: 10 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: web-app template: metadata: labels: app: web-app version: v1.0.0 annotations: prometheus.io/scrape: “true” prometheus.io/port: “8080” prometheus.io/path: “/metrics” spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - web-app topologyKey: kubernetes.io/hostname containers: - name: web-app image: myregistry/web-app:v1.0.0 imagePullPolicy: Always ports: - containerPort: 8080 name: http protocol: TCP env: - name: DATABASE_URL valueFrom: secretKeyRef: name: db-secret key: url - name: LOG_LEVEL valueFrom: configMapKeyRef: name: app-config key: log.level resources: requests: memory: “256Mi” cpu: “250m” limits: memory: “512Mi” cpu: “500m” livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 successThreshold: 1 volumeMounts: - name: app-config mountPath: /etc/config readOnly: true - name: temp-storage mountPath: /tmp securityContext: runAsNonRoot: true runAsUser: 1000 readOnlyRootFilesystem: true allowPrivilegeEscalation: false volumes: - name: app-config configMap: name: app-config - name: temp-storage emptyDir: {} serviceAccountName: web-app-sa securityContext: fsGroup: 2000 — apiVersion: v1 kind: Service metadata: name: web-app-service namespace: production spec: type: ClusterIP selector: app: web-app ports:
- port: 80 targetPort: 8080 protocol: TCP name: http — apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-app-hpa namespace: production spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-app minReplicas: 3 maxReplicas: 10 metrics:
- type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70
- type: Resource resource: name: memory target: type: Utilization averageUtilization: 80
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: “1000”
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent value: 50 periodSeconds: 60 scaleUp: stabilizationWindowSeconds: 0 policies:
- type: Percent value: 100 periodSeconds: 15
- type: Pods value: 4 periodSeconds: 15 selectPolicy: Max `
マネージドKubernetesサービスの比較
Amazon EKS(Elastic Kubernetes Service)
`yaml
EKS特有の機能活用
ALB Ingress Controller
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-app-ingress namespace: production annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:123456789012:certificate/xxx alb.ingress.kubernetes.io/listen-ports: ‘[{“HTTP”: 80}, {“HTTPS”: 443}]’ alb.ingress.kubernetes.io/ssl-redirect: ‘443’ alb.ingress.kubernetes.io/healthcheck-path: /health alb.ingress.kubernetes.io/healthcheck-interval-seconds: ‘15’ alb.ingress.kubernetes.io/healthcheck-timeout-seconds: ‘5’ alb.ingress.kubernetes.io/healthy-threshold-count: ‘2’ alb.ingress.kubernetes.io/unhealthy-threshold-count: ‘2’ spec: rules:
- host: app.example.com
http:
paths:
- path: /* pathType: ImplementationSpecific backend: service: name: web-app-service port: number: 80
IRSA (IAM Roles for Service Accounts)
apiVersion: v1 kind: ServiceAccount metadata: name: web-app-sa namespace: production annotations: eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/WebAppRole `
Google GKE(Google Kubernetes Engine)
`yaml
GKE Autopilot モード設定
apiVersion: v1 kind: ResourceQuota metadata: name: compute-quota namespace: production spec: hard: requests.cpu: “1000” requests.memory: 1000Gi persistentvolumeclaims: “10”
Workload Identity
apiVersion: v1 kind: ServiceAccount metadata: name: web-app-sa namespace: production annotations: iam.gke.io/gcp-service-account: web-app@project-id.iam.gserviceaccount.com
GKE Ingress with Cloud Load Balancer
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-app-ingress namespace: production annotations: kubernetes.io/ingress.global-static-ip-name: “web-app-ip” networking.gke.io/managed-certificates: “web-app-cert” kubernetes.io/ingress.class: “gce” spec: defaultBackend: service: name: web-app-service port: number: 80 `
Amazon ECS:シンプルさを追求
タスク定義によるコンテナ管理
json
{
"family": "web-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"taskRoleArn": "arn:aws:iam::123456789012:role/WebAppTaskRole",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "web-app",
"image": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/web-app:latest",
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"essential": true,
"environment": [
{
"name": "ENVIRONMENT",
"value": "production"
}
],
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:db-url-xxxxx"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/web-app",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 60
}
}
]
}
ECSサービスの設定
`python
CDKによるECSサービス定義
from aws_cdk import ( aws_ecs as ecs, aws_ec2 as ec2, aws_elasticloadbalancingv2 as elbv2, aws_logs as logs, aws_autoscaling as autoscaling, core )
class EcsServiceStack(core.Stack): def init(self, scope: core.Construct, id: str, **kwargs): super().init(scope, id, **kwargs)
# VPCの作成
vpc = ec2.Vpc(
self, "ServiceVpc",
max_azs=2,
nat_gateways=2
)
# ECSクラスター
cluster = ecs.Cluster(
self, "Cluster",
vpc=vpc,
container_insights=True
)
# Fargateサービス
task_definition = ecs.FargateTaskDefinition(
self, "TaskDef",
memory_limit_mib=1024,
cpu=512
)
container = task_definition.add_container(
"WebContainer",
image=ecs.ContainerImage.from_registry("myapp:latest"),
logging=ecs.LogDrivers.aws_logs(
stream_prefix="ecs",
log_retention=logs.RetentionDays.ONE_WEEK
),
environment={
"ENVIRONMENT": "production"
}
)
container.add_port_mappings(
ecs.PortMapping(
container_port=8080,
protocol=ecs.Protocol.TCP
)
)
# ALB
lb = elbv2.ApplicationLoadBalancer(
self, "ALB",
vpc=vpc,
internet_facing=True
)
# Fargateサービス
service = ecs.FargateService(
self, "Service",
cluster=cluster,
task_definition=task_definition,
desired_count=3,
assign_public_ip=False,
service_name="web-app"
)
# オートスケーリング
scaling = service.auto_scale_task_count(
min_capacity=3,
max_capacity=10
)
scaling.scale_on_cpu_utilization(
"CpuScaling",
target_utilization_percent=70
)
scaling.scale_on_request_count(
"RequestScaling",
requests_per_target=1000,
target_group=target_group
) `
9.4 コンテナレジストリとCI/CD連携
コンテナレジストリの戦略的活用
コンテナレジストリは、コンテナイメージのライフサイクル管理において中心的な役割を果たします。
マルチステージレジストリ戦略
`yaml
レジストリ構成
registries: development: url: dev-registry.company.com retention: 7days scan: on-push
staging: url: staging-registry.company.com retention: 30days scan: daily approval: automated
production: url: prod-registry.company.com retention: 1year scan: continuous approval: manual signing: required `
Amazon ECR(Elastic Container Registry)
ライフサイクルポリシーによる自動管理
json
{
"rules": [
{
"rulePriority": 1,
"description": "Keep last 10 production images",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["prod-"],
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 2,
"description": "Expire untagged images after 1 day",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 1
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 3,
"description": "Keep only 3 dev images",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["dev-"],
"countType": "imageCountMoreThan",
"countNumber": 3
},
"action": {
"type": "expire"
}
}
]
}
脆弱性スキャンの自動化
`python
ECRスキャン結果の監視
import boto3 import json from datetime import datetime
ecr = boto3.client(‘ecr’)
def lambda_handler(event, context): “”” ECRスキャン完了時に呼び出され、脆弱性を評価 “”” detail = event[‘detail’] repository_name = detail[‘repository-name’] image_digest = detail[‘image-digest’]
# スキャン結果を取得
response = ecr.describe_image_scan_findings(
repositoryName=repository_name,
imageId={'imageDigest': image_digest}
)
findings = response['imageScanFindings']
severity_counts = findings.get('findingSeverityCounts', {})
# 重大度別の脆弱性数
critical = severity_counts.get('CRITICAL', 0)
high = severity_counts.get('HIGH', 0)
medium = severity_counts.get('MEDIUM', 0)
low = severity_counts.get('LOW', 0)
# ポリシーチェック
if critical > 0:
# 本番環境へのデプロイをブロック
send_alert(
f"CRITICAL: {repository_name} has {critical} critical vulnerabilities",
severity="critical"
)
tag_image_as_unsafe(repository_name, image_digest)
elif high > 5:
# 警告を発行
send_alert(
f"WARNING: {repository_name} has {high} high vulnerabilities",
severity="warning"
)
# 詳細レポートの生成
generate_vulnerability_report(repository_name, findings)
return {
'statusCode': 200,
'body': json.dumps({
'repository': repository_name,
'critical': critical,
'high': high,
'medium': medium,
'low': low
})
} `
包括的なCI/CDパイプライン
GitHub Actions による完全自動化
`yaml
.github/workflows/container-pipeline.yml
name: Container CI/CD Pipeline
on: push: branches: [main, develop] pull_request: branches: [main]
env: REGISTRY: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com REPOSITORY: web-app
jobs: # コード品質チェック code-quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
GITHUB_TOKEN: ``${{ secrets.GITHUB_TOKEN }}``
SONAR_TOKEN: ``${{ secrets.SONAR_TOKEN }}``
- name: Check Quality Gate
uses: sonarsource/sonarqube-quality-gate-action@master
timeout-minutes: 5
env:
SONAR_TOKEN: ``${{ secrets.SONAR_TOKEN }}``
# コンテナビルドとスキャン build-and-scan: needs: code-quality runs-on: ubuntu-latest permissions: id-token: write contents: read
steps:
- uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ``${{ secrets.AWS_ROLE_ARN }}``
aws-region: ap-northeast-1
- name: Login to ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Image
uses: docker/build-push-action@v4
with:
context: .
push: false
tags: ``${{ env.REGISTRY }}``/``${{ env.REPOSITORY }}``:``${{ github.sha }}``
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=docker,dest=/tmp/image.tar
- name: Run Trivy Scanner
uses: aquasecurity/trivy-action@master
with:
input: /tmp/image.tar
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy Results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: Run Snyk Container Test
run: |
docker load -i /tmp/image.tar
snyk container test ``${{ env.REGISTRY }}``/``${{ env.REPOSITORY }}``:``${{ github.sha }}`` \
--severity-threshold=high \
--file=Dockerfile
# パフォーマンステスト performance-test: needs: build-and-scan runs-on: ubuntu-latest steps: - uses: actions/checkout@v3
- name: Run Load Tests
uses: grafana/k6-action@v0.3.0
with:
filename: tests/load-test.js
- name: Upload Results
uses: actions/upload-artifact@v3
with:
name: k6-results
path: results.json
# プロダクション展開 deploy-production: needs: [build-and-scan, performance-test] if: github.ref == ‘refs/heads/main’ runs-on: ubuntu-latest environment: production
steps:
- uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ``${{ secrets.AWS_PROD_ROLE_ARN }}``
aws-region: ap-northeast-1
- name: Push to ECR
run: |
docker load -i /tmp/image.tar
docker tag ``${{ env.REGISTRY }}``/``${{ env.REPOSITORY }}``:``${{ github.sha }}`` \
``${{ env.REGISTRY }}``/``${{ env.REPOSITORY }}``:production
docker push ``${{ env.REGISTRY }}``/``${{ env.REPOSITORY }}``:production
- name: Update ECS Service
run: |
aws ecs update-service \
--cluster production \
--service web-app \
--force-new-deployment
- name: Wait for Deployment
run: |
aws ecs wait services-stable \
--cluster production \
--services web-app
- name: Run Smoke Tests
run: |
npm install
npm run test:smoke -- --env=production `
イメージ管理のベストプラクティス
セマンティックバージョニングとタグ戦略
`python
自動タグ生成スクリプト
import re import subprocess from datetime import datetime
def generate_tags(branch, commit_sha, existing_tags): “”” ブランチとコミット情報から適切なタグを生成 “”” tags = []
# 基本タグ
tags.append(commit_sha[:8])
tags.append(f"{branch}-{commit_sha[:8]}")
# ブランチ別タグ戦略
if branch == "main":
# セマンティックバージョン
version = get_next_version(existing_tags)
tags.extend([
version,
"latest",
f"v{version}"
])
elif branch == "develop":
# 開発版タグ
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
tags.extend([
"develop",
f"develop-{timestamp}"
])
elif branch.startswith("release/"):
# リリース候補
version = branch.split("/")[1]
tags.extend([
f"rc-{version}",
f"rc-{version}-{commit_sha[:8]}"
])
return tags
def get_next_version(existing_tags): “”” 既存のタグから次のバージョンを決定 “”” versions = [] for tag in existing_tags: match = re.match(r’^v?(\d+).(\d+).(\d+)$’, tag) if match: versions.append(( int(match.group(1)), int(match.group(2)), int(match.group(3)) ))
if not versions:
return "1.0.0"
# 最新バージョンを取得
latest = max(versions)
# パッチバージョンをインクリメント
return f"{latest[0]}.{latest[1]}.{latest[2] + 1}" `
コンテナのサプライチェーンセキュリティ
イメージ署名とポリシー
`yaml
Notary/DCTによるイメージ署名
docker-compose.yml
version: ‘3.8’
services: notary-server: image: notary:server environment: - NOTARY_SERVER_DB_URL=mysql://notary@mysql:3306/notaryserver depends_on: - mysql
notary-signer: image: notary:signer environment: - NOTARY_SIGNER_DB_URL=mysql://notary@mysql:3306/notarysigner
mysql: image: mysql:5.7 environment: - MYSQL_ALLOW_EMPTY_PASSWORD=yes volumes: - notary-data:/var/lib/mysql
署名ポリシー
admission-policy: rules: - name: require-signature match: namespaces: [“production”] require: - signature: keys: - keyless: issuer: https://token.actions.githubusercontent.com subject: repo:myorg/myrepo:ref:refs/heads/main `
サーバーレスとコンテナは、それぞれ異なる強みを持つクラウドネイティブ技術です。サーバーレスはイベント駆動で断続的なワークロードに最適であり、コンテナは複雑なアプリケーションや継続的な処理に適しています。重要なのは、それぞれの特性を理解し、適切なワークロードに適切な技術を選択することです。両者を組み合わせることで、スケーラブルで効率的、かつ管理しやすいクラウドネイティブアーキテクチャを実現できます。
第10章へ進む