EKSのBlue/Greenデプロイを試してみた

EKSのBlue/Greenデプロイを試してみた

2025.10.31

はじめに

以前、ECSのネイティブなBlue/Greenデプロイを試してみたという記事で、ECS で Blue/Green デプロイを実装しました。
今回は、同じような構成を EKS(Amazon Elastic Kubernetes Service)+ Argo Rollouts で構築してみます。Argo Rollouts は Kubernetes 上で Blue/Green デプロイや Canary デプロイを宣言的に管理できるツールです。

前回記事との比較ポイント:

  • ECS: タスク定義のリビジョン更新 → ECS サービスが自動で Blue/Green デプロイ
  • EKS: Rollout マニフェスト更新 → Argo Rollouts が自動で Blue/Green デプロイ

どちらも、マニフェスト(タスク定義 / Rollout)を更新するだけで、自動的にトラフィック切替が行われます。

今回作るもの

前回の ECS 構成と同じように、以下を実装します。

  • ALB + 2 つの Service(Blue/Green)
  • アプリケーション(Flask)の v1 → v2 デプロイ
  • 自動トラフィック切替

blog_05

前提条件

本記事を実施するには、以下のリソースが必要です。既にお持ちの場合はそのまま使用し、ない場合は新規作成するか、以下の CloudFormation テンプレートで作成してください。

必要なリソース(VPC 構成):

リソース 数量 用途
VPC 1 10.0.0.0/16
パブリックサブネット 2 ALB 配置用
プライベートサブネット 2 Fargate Pod 配置用
Internet Gateway 1 パブリックサブネット用
NAT Gateway 1 プライベートサブネット用(※)
Elastic IP 1 NAT Gateway 用

※本記事では、コスト削減のため NAT Gateway を 1 つのみ使用しています。

eks-vpc-template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'EKS用VPC'

Parameters:
  VpcCIDR:
    Type: String
    Default: 10.0.0.0/16
  PublicSubnet1CIDR:
    Type: String
    Default: 10.0.0.0/24
  PublicSubnet2CIDR:
    Type: String
    Default: 10.0.1.0/24
  PrivateSubnet1CIDR:
    Type: String
    Default: 10.0.10.0/24
  PrivateSubnet2CIDR:
    Type: String
    Default: 10.0.11.0/24

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: eks-test-vpc

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: eks-test-igw

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnet1CIDR
      AvailabilityZone: !Select [0, !GetAZs '']
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: eks-test-public-1a
        - Key: kubernetes.io/role/elb
          Value: 1

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnet2CIDR
      AvailabilityZone: !Select [1, !GetAZs '']
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: eks-test-public-1c
        - Key: kubernetes.io/role/elb
          Value: 1

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PrivateSubnet1CIDR
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - Key: Name
          Value: eks-test-private-1a
        - Key: kubernetes.io/role/internal-elb
          Value: 1

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PrivateSubnet2CIDR
      AvailabilityZone: !Select [1, !GetAZs '']
      Tags:
        - Key: Name
          Value: eks-test-private-1c
        - Key: kubernetes.io/role/internal-elb
          Value: 1

  NatGatewayEIP:
    Type: AWS::EC2::EIP
    DependsOn: AttachGateway
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: eks-test-nat

  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: eks-test-nat

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: eks-test-public-rt

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: eks-test-private-rt

  DefaultPrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet1

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet2

Outputs:
  VpcId:
    Value: !Ref VPC
  PrivateSubnet1Id:
    Value: !Ref PrivateSubnet1
  PrivateSubnet2Id:
    Value: !Ref PrivateSubnet2

その他:

  • ECR イメージ:前回記事で作成した Flask アプリのイメージ(v1, v2)

既存の ECR イメージがあればそのまま使用できます。ない場合は、前回記事の STEP 3 を参考に ECR へのプッシュを実施してください。

STEP 1: EKS クラスター作成

EKS クラスターを作成します。これは、Kubernetes のコントロールプレーンを提供し、後続のステップでデプロイするアプリケーションを管理する基盤となります。

クラスターの作成

EKS コンソール → クラスター → 「クラスターを作成」

設定オプション:

  • カスタム設定を選択
  • EKS オートモード:オフ

※ EKS オートモードは、コンピューティング(ノードの自動プロビジョニング)、ストレージ(EBS ボリュームの自動管理)、ネットワーキング(VPC CNI の自動設定)を自動管理する機能です。本記事では、各コンポーネントの動作を理解するため、手動設定を選択しています。

クラスター設定:

  • クラスター名:eks-cluster-test
  • Kubernetes バージョン:1.31(最新推奨)
  • クラスター IAM ロール:推奨ロールを作成

その他の設定はデフォルトのまま「次へ」をクリックします。

スクリーンショット 2025-10-21 15.21.19


ネットワーキング設定:

  • VPC:eks-test-vpc(CloudFormation で作成した VPC)
  • サブネット:
    • eks-test-private-1a(プライベートサブネット)
    • eks-test-private-1c(プライベートサブネット)

※ EKS クラスター作成時に指定するサブネットは、Kubernetes API サーバー(コントロールプレーン)との通信用 ENI(Elastic Network Interface)が配置される場所です。

  • 追加のセキュリティグループ:選択しない

  • クラスターエンドポイントアクセス:パブリックおよびプライベート

「次へ」をクリックします。


オブザーバビリティ設定:

以下のすべてのログタイプを有効化:

  • API サーバー
  • 監査
  • Authenticator
  • コントローラーマネージャー
  • スケジューラ

※ コントロールプレーンのログを有効化することで、クラスターの動作を詳細に確認でき、トラブルシューティングに役立ちます。

スクリーンショット 2025-10-21 15.38.59

「次へ」をクリックします。


アドオンの選択:

以下の 3 つのアドオンのみを選択:

  • Amazon VPC CNI
  • kube-proxy
  • CoreDNS

※ これらは Kubernetes クラスターの基本的なネットワーク機能を提供するアドオンです。その他のアドオン(AWS Load Balancer Controller、Argo Rollouts)は後続の STEP で手動インストールします。

「次へ」をクリックします。


アドオン設定の構成:

  • Amazon VPC CNI:サービスアカウントの Pod Identity IAM ロール → 推奨ロールを作成

※ ENI の作成・管理や IP アドレスの割り当て・解放などの AWS API 操作を実行するために IAM ロールが必要です。

  • kube-proxy:デフォルト設定のまま
  • CoreDNS:デフォルト設定のまま

スクリーンショット 2025-10-21 15.48.40

「次へ」をクリックします。


確認と作成:

設定内容を確認し、「作成」をクリックします。


STEP 2: CloudShell 環境のセットアップ

EKS クラスターを操作するための環境を整えます。kubectl でクラスターに接続し、必要なツール(eksctl、helm)をインストールします。

環境セットアップコマンド
# 環境変数設定
export CLUSTER_NAME=eks-cluster-test
export REGION=ap-northeast-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export VPC_ID=$(aws eks describe-cluster --name ${CLUSTER_NAME} --region ${REGION} --query 'cluster.resourcesVpcConfig.vpcId' --output text)

# EKS クラスターに接続
aws eks update-kubeconfig --name ${CLUSTER_NAME} --region ${REGION}

# flask-app Namespace 作成
kubectl create namespace flask-app

# eksctl のインストール
curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/local/bin

# helm のインストール
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

STEP 3: 必要なコンポーネントのインストール

EKS クラスターでアプリケーションをインターネットに公開し、Blue/Green デプロイを実現するために、以下の 2 つのコンポーネントをインストールします。

インストールするコンポーネント:

  1. AWS Load Balancer Controller: Ingress リソースから ALB を自動作成
  2. Argo Rollouts: Blue/Green デプロイを自動化

AWS Load Balancer Controller のインストール

Kubernetes の Ingress リソースから自動的に ALB を作成・管理するために必要なコントローラーです。これにより、マニフェストファイルだけで ALB の設定が完結します。

IAM ポリシーとサービスアカウントの作成
# IAM ポリシーのダウンロード
curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.11.0/docs/install/iam_policy.json

# IAM ポリシー作成
aws iam create-policy \
    --policy-name AWSLoadBalancerControllerIAMPolicy \
    --policy-document file://iam_policy.json

# OIDC プロバイダーの関連付け
eksctl utils associate-iam-oidc-provider --region=${REGION} --cluster=${CLUSTER_NAME} --approve

# サービスアカウント作成
eksctl create iamserviceaccount \
  --cluster=${CLUSTER_NAME} \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --attach-policy-arn=arn:aws:iam::${ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy \
  --approve
Fargate プロファイルの作成
# kube-system 用 Fargate プロファイル作成
eksctl create fargateprofile \
  --cluster ${CLUSTER_NAME} \
  --name kube-system-profile \
  --namespace kube-system

# flask-app 用 Fargate プロファイル作成
eksctl create fargateprofile \
  --cluster ${CLUSTER_NAME} \
  --name flask-app-profile \
  --namespace flask-app
Helm でコントローラーをインストール
# Helm リポジトリ追加
helm repo add eks https://aws.github.io/eks-charts
helm repo update

# AWS Load Balancer Controller インストール
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=${CLUSTER_NAME} \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller \
  --set vpcId=${VPC_ID}

# インストール確認
kubectl get deployment -n kube-system aws-load-balancer-controller

Argo Rollouts のインストール

Kubernetes で Blue/Green デプロイや Canary デプロイを実現するコントローラーです。通常の Deployment では実現できない、トラフィックの切替制御を自動化します。

Argo Rollouts セットアップ
# argo-rollouts 用 Fargate プロファイル作成
eksctl create fargateprofile \
  --cluster ${CLUSTER_NAME} \
  --name argo-rollouts-profile \
  --namespace argo-rollouts

# Argo Rollouts インストール
kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml

# インストール確認
kubectl get pods -n argo-rollouts

STEP 4: v1 アプリケーションのデプロイ

Blue/Green デプロイに必要なリソースを作成し、v1 アプリケーションをデプロイします。
以下で、本番トラフィック用(Blue)とプレビュー用(Green)の 2 つの Service を作成します。

Blue/Green 用 Service の作成
cat > service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
  name: flask-app-blue
  namespace: flask-app
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
  selector:
    app: flask-app
---
apiVersion: v1
kind: Service
metadata:
  name: flask-app-green
  namespace: flask-app
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
  selector:
    app: flask-app
EOF

# 適用
kubectl apply -f service.yaml

次に、ALB を自動作成するための Ingress リソースを作成します。

Ingress の作成(ALB)
# パブリックサブネット ID を取得
PUBLIC_SUBNET_1=$(aws ec2 describe-subnets --filters "Name=tag:Name,Values=eks-test-public-1a" --query 'Subnets[0].SubnetId' --output text)
PUBLIC_SUBNET_2=$(aws ec2 describe-subnets --filters "Name=tag:Name,Values=eks-test-public-1c" --query 'Subnets[0].SubnetId' --output text)

# Ingress 作成
cat > ingress.yaml << EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: flask-app
  namespace: flask-app
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/subnets: ${PUBLIC_SUBNET_1},${PUBLIC_SUBNET_2}
    alb.ingress.kubernetes.io/healthcheck-path: /health
spec:
  ingressClassName: alb
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: flask-app-blue
            port:
              number: 80
EOF

# 適用
kubectl apply -f ingress.yaml

最後に、Blue/Green デプロイを管理する Rollout リソースを作成し、v1 をデプロイします。

Rollout の作成( v1 デプロイ)
cat > rollout.yaml << EOF
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: flask-app
  namespace: flask-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
    spec:
      containers:
      - name: flask-app
        image: ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/flask-app-yakame:v1
        ports:
        - containerPort: 8080
        env:
        - name: APP_VERSION
          value: "v1"
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
  strategy:
    blueGreen:
      activeService: flask-app-blue
      previewService: flask-app-green
      autoPromotionEnabled: true
      autoPromotionSeconds: 60 # Green 環境が正常であれば 60 秒後に自動的に Blue に昇格
      scaleDownDelaySeconds: 30 # 旧バージョンの Pod を 30 秒後に削除
EOF

# 適用
kubectl apply -f rollout.yaml

v1 デプロイ確認

デプロイが完了したら、以下のコマンドで Pod の状態を確認します。

kubectl get pods -n flask-app

期待される出力:

NAME                         READY   STATUS    RESTARTS   AGE
flask-app-xxxxxxxxxx-xxxxx   1/1     Running   0          2m

ALB 経由でアクセス確認 をしてみると以下の表示がされます。
v1チェック

Hello from ECS Fargate! Version: v1」と表示されれば、v1 のデプロイが成功しています。

STEP 5: v2 への Blue/Green デプロイ

v1 から v2 への Blue/Green デプロイを実行します。Rollout マニフェストを更新することで、Argo Rollouts が自動的にトラフィック切替を行います。

Rollout を v2 に更新
cat > rollout-v2.yaml << EOF
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: flask-app
  namespace: flask-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
    spec:
      containers:
      - name: flask-app
        image: ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/flask-app-yakame:v2
        ports:
        - containerPort: 8080
        env:
        - name: APP_VERSION
          value: "v2"
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
  strategy:
    blueGreen:
      activeService: flask-app-blue
      previewService: flask-app-green
      autoPromotionEnabled: true
      autoPromotionSeconds: 60
      scaleDownDelaySeconds: 30
EOF

kubectl apply -f rollout-v2.yaml

v2 デプロイ確認

デプロイ直後の状態(v1 と v2 が共存)

kubectl get pods -n flask-app

スクリーンショット 2025-10-22 14.57.49

v2 デプロイ直後は、v1(Blue)と v2(Green)の 2 つの Pod が共存します。この時点では、まだ v1 が本番トラフィックを受け付けており、v2 はプレビュー環境として起動しています。

60 秒後:トラフィック切替完了
v2チェック

Hello from ECS Fargate! Version: v2」と表示されれば、v2 のデプロイが成功しています。

切替完了後の Pod 状態
スクリーンショット 2025-10-22 14.57.55

v1 の Pod が削除され、v2 の Pod のみになっていることを確認できます。

デプロイの詳細な流れ
以下のコマンドより、デプロイの詳細な流れを確認できます。

kubectl describe rollout flask-app -n flask-app

スクリーンショット 2025-10-22 15.16.20

デプロイの流れ(Events より):

  1. 新しい ReplicaSet 作成:v2 用の Pod が起動
  2. Green サービスに切替:flask-app-green が v2 を指すように更新
  3. Paused 状態:60 秒間、v2 の健全性を監視(この間はまだ v1 が本番トラフィックを受信)
  4. Blue サービスに切替:flask-app-blue(本番トラフィック)が v2 を指すように更新
  5. 旧 ReplicaSet をスケールダウン:v1 の Pod が削除される

この一連の流れにより、ダウンタイムなしで v1 から v2 へのトラフィック切替が完了しています。

おわりに

本記事では、Amazon EKS(Fargate)Argo Rollouts を使った Blue/Green デプロイ を実装しました。前回の ECS 記事と同様に、マニフェストの更新だけで自動的にトラフィック切替が行われることを確認できました。

参考文献

アノテーション株式会社について

アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。当社は様々な職種でメンバーを募集しています。「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。

この記事をシェアする

FacebookHatena blogX

関連記事