Amazon Web Services ブログ

Amazon ECS での Bottlerocket の始め方 〜 コンテナ向けのセキュアな Linux ディストリビューション

この記事は Getting started with Bottlerocket and Amazon ECS を翻訳したものです。

2021年 6 月 30 日に、Amazon ECS に最適化された Bottlerocket の AMI が一般提供され、Amazon ECS が Bottlerocket をサポートしたことを発表しました。Bottlerocket はセキュリティとメンテナンス性を重視し、コンテナベースのワークロードをホスティングするための信頼性と一貫性のある Linux ディストリビューションを提供するオープンソースプロジェクトです。

この記事では、Amazon ECS で Bottlerocket を利用して、安全で堅牢な方法でアプリケーションを実行する方法をご紹介します。

なぜ Bottlerocket なのか

お客様はワークロードの実行にコンテナを採用し続けており、AWS はこれらのコンテナ化されたアプリケーションを実行するために設計、最適化された Linux ディストリビューションの必要性を感じていました。Bottlerocket OS は、コンテナを実行するホストに安全な基盤を提供し、コンテナを大規模に管理するための運用上のオーバーヘッドを最小化するために構築されました。

セキュリティ

Bottlerocket のルートファイルシステムは読み取り専用になっており、ユーザースペースのプロセスが直接変更することはできません。Bottlerocket は、ルートファイルシステムイメージに dm-verity を使用しています。dm-verity は改ざんをチェックするために暗号化ダイジェストを使用してブロックデバイスの透過的な整合性チェックを提供する Linux カーネルモジュールです。これにより CVE-2019-5736 などの、いくつかのコンテナエスケープの脆弱性から保護されます。カーネルは破損が検出された場合に再起動するように構成されており、基盤となるブロックデバイスが予期せず変更された場合にシステムをフェールクローズさせることができます。

ユーザーは /etc/resolv.conf/etc/containerd/config.toml などのシステム構成ファイルを直接変更することはできません。ファイルの変更は API を介して行われます。API は HTTP インターフェイスとしてインスタンス上でローカルで実行され、OS の設定を読み取り、変更したり、それらの設定に基づいてサービスを更新したり、OS の状態を確認して変更するための主要な方法となります。

これらの設定は再起動後も保持され、OS のアップグレードでも設定が移行されます。また、起動時にテンプレートからシステム設定ファイルを作成するためにも使用されます。Bottlerocket で利用可能な実行可能ファイルはハードニングフラグを使用してビルドされているため、攻撃者がメモリ破損の脆弱性を悪用しようとした場合にアドレスを予測するのが難しくなります。

最後に、Bottlerocket はデフォルトで SELinux を有効にし、enforcing モードを設定してブート中にポリシーをロードします。無効にする方法はありません。

運用タスクの簡易化

Bottlerocket は、自動化によって適用することができる信頼性の高いアップデートを目的として設計されています。

これは、次のメカニズムによって実現されます。

  • OS イメージを active/passive で切り替えるための 2 つのパーティションセット
  • ランタイムの構成のために、モデル化された設定を持つ宣言型 API

Bottlerocket を実行しているインスタンスのアップグレードは、物理的なデバイスのファームウェアのアップグレードや、あなたのスマホの OS にアップデートを適用するのと似ています。新しいバージョンを入手するために、たくさんのソフトウェアパッケージをインストールする必要はありません。システム全体のディスクイメージをダウンロードしてアップデートを適用すると、代替のパーティションセットに書き込まれます。アップグレードを有効にする準備ができたらインスタンスを再起動します。このとき、新バージョンの OS がインストールされたプライマリのパーティションから起動します。すべてのデータと永続的な設定は別のデータパーティションに保存されており、再起動後も利用可能です。

Bottlerocket でのユーザー操作

ユーザーは基盤となる OS に直接アクセスすることはできないため、OS の特定のインターフェイスを提供する 2 つのコンテナを操作します(デフォルトではインスタンスに SSH 接続することはできません)。

Bottlerocket にはデフォルトで有効になっている「コントロール (Control)」コンテナがあり、これらはオーケストレーターの外で containerd の別インスタンスとして実行されます。このコンテナは EC2 で動作する Bottlerocket インスタンスでコマンドを実行したり、シェルのセッションを開始できる AWS SSM エージェントを実行します(このコントロールコンテナを独自のものに置き換えることができます。詳細については、ドキュメントを参照してください)。

SSM を介してコンテナにアクセスするために、インスタンスに適切な IAM ロールを与える必要があります(これについては記事の後半で紹介します)。

コントロールコンテナにアクセスできるようになると、コマンドの実行が可能になり、インスタンスで実行されているローカルサービスに対して適切な API 呼び出しを行い、Bottlerocket インスタンスを設定および管理できます。

これは本格的なシェル環境ではなく、使用できるコマンドセットは限られています。ほとんどの API のやり取りは apiclient コマンドを介して行われます。詳細については、ドキュメントを参照してください。

Bottlerocket には「管理 (Admin)」コンテナもあります。これはデフォルトで無効になっており containerd とは別のプロセスで実行されます。このコンテナには SSH サーバーが実行されており、EC2 に登録された SSH キーを使用して ec2-user としてログインできます。

Bottlerocket はオーケストレータが管理するワークロード (ECS または Kubernetes を介して管理されるお客様のワークロード) を、システムのワークロード (「管理」および「コントロール」コンテナ) から分離するために、2 つの別々のコンテナランタイムのコピーを使用します。これにより、オーケストレータが管理するワークロードで発生する可能性のある問題の Blast Radius を減らし、基盤システムの機能を維持できます。ECS バリアントの場合、Docker はオーケストレータが管理するコンテナに使用され、containerd はホストコンテナに使用されます(「バリアント」については後述します)。

管理コンテナを有効にするには 2 つの方法があります。AWS マネジメントコンソールまたは AWS CLI から EC2 インスタンスを起動するときに、ユーザーデータで設定を構成できます。

[settings.host-containers.admin]
enabled = true

ローカルのシェルから次のコマンドを実行して、コントロールコンテナ内から管理コンテナを有効化することもできます。

enable-admin-container

管理コンテナがアクティブになると、インスタンスの起動時に指定したキーペアに一致するプライベートキーを使用して SSH 経由でインスタンスにアクセスできます。Bottlerocket インスタンスに SSH 接続すると、強い特権を持つ Amazon Linux 2 コンテナに入ります。管理コンテナには .bottleRocket/root にマウントされたホストファイルシステムがあり、AL2 パッケージの完全なエコシステムを自由に使用できます。この管理コンテナはホストの PID 名前空間でも (またホストと共有する他の名前空間でも) 動作します。つまり、ps のようなコマンドはホストのプロセスリストを表示します。sheltie を使用すると、ホストの他の名前空間にジャンプでき、静的な Bash シェルを使用してホストにさらに近づくことができます。

上記で 「ECS バリアント」について言及しましたが、バリアントについてもう少し詳しく説明する必要があると思います。バリアントとは、必要なソフトウェアのみをインストールしたリストに API を定義したモデルを加えたものです。セキュリティとパフォーマンスの観点から、Bottlerocket のフットプリントをできるだけ小さくしたいと考えています。そのため、ユースケースに合わせてカスタマイズされたバリエーションがあり、それぞれ独自のソフトウェアと API 設定があります。バリアントの詳細とそのビルド方法については、Bottlerocket のドキュメントを参照してください。

ECS クラスターでの Bottlerocket のセットアップ

セットアップが実際にどのように行われているか見てみましょう。以下の手順では us-west-2 (オレゴン) リージョンを使用します。

前提条件

  • 適切な認証情報を設定した AWS CLI
  • 選択したリージョンのデフォルト VPC (AWS アカウント内の既存の VPC を使用することも可能)
  • AWS アカウント内のリモートアクセス用のキーペア (手順内では ecs-botterocketを使用)

はじめに、ecs-bottlerocketという名前の ECS クラスターを作成します。

aws ecs --region us-west-2 create-cluster --cluster-name ecs-bottlerocket

起動するインスタンスは、ECS API と AWS Systems Manager セッションマネージャー API の両方と通信するために IAM ロールが必要になります。AmazonSSMManagedInstanceCoreAmazonEC2ContainerServiceforEC2Role の両方の管理ポリシーがアタッチされている ecsInstanceRole という名前の IAM ロールを作成します。

AMI のリストは AWS Systems Manager パラメータストアから公開されているため、最新の Bottlerocket リリースの AMI ID を取得します。

latest_bottlerocket_ami=$(aws ssm get-parameter --region us-west-2 \
    --name "/aws/service/bottlerocket/aws-ecs-1/x86_64/latest/image_id" \
    --query Parameter.Value --output text)

まず、デフォルトVPC の ID を取得します。

vpc_id=$(aws ec2 describe-vpcs \
   --region us-west-2 \
   --filters=Name=isDefault,Values=true \
   --query 'Vpcs[].VpcId' --output text)

次に、パブリック IP アドレスを割り当てるように構成されたサブネットのリストを取得します。

aws ec2 describe-subnets \
   --region us-west-2 \
   --filter=Name=vpc-id,Values=$vpc_id \
   --query 'Subnets[?MapPublicIpOnLaunch == `true`].SubnetId'
   
[
    "subnet-bc8993e6",
    "subnet-b55f6bfe",
    "subnet-e1e27fca",
    "subnet-21cbc058"
]

EC2 インスタンスを ECS クラスターに関連付けるには、インスタンスの作成時にいくつかの情報を提供する必要があります。この ECS クラスターの詳細情報を持つ小さな設定ファイルをカレントディレクトリに保存します。サポートされている設定の詳細はこちらにあります。

cat > ./userdata.toml << 'EOF'
[settings.ecs]
cluster = "ecs-bottlerocket"
EOF

上記の 2 つのサブネットにそれぞれ 1 つずつ、計 2 つの Bottlerocket インスタンスをデプロイします。この記事では 2 つのパブリックサブネットを選択しますが、これはデバッグやインスタンスへの接続が必要な場合に手順を簡潔にするためであり、実際はユースケースに基づいてプライベートサブネットまたはパブリックサブネットを選択してください。

aws ec2 run-instances --key-name ecs-bottlerocket \
   --subnet-id subnet-bc8993e6 \
   --image-id $latest_bottlerocket_ami \
   --instance-type t3.large \
   --region us-west-2 \
   --tag-specifications 'ResourceType=instance,Tags=[{Key=bottlerocket,Value=quickstart}]' \
   --user-data file://userdata.toml \
   --iam-instance-profile Name=ecsInstanceRole
   
aws ec2 run-instances --key-name ecs-bottlerocket \
   --subnet-id subnet-b55f6bfe \
   --image-id $latest_bottlerocket_ami \
   --instance-type t3.large \
   --region us-west-2 \
   --tag-specifications 'ResourceType=instance,Tags=[{Key=bottlerocket,Value=quickstart}]' \
   --user-data file://userdata.toml \
   --iam-instance-profile Name=ecsInstanceRole 

環境の状態を確認したいと思います。まずクラスターを見てみましょう。ここではアクティブでな 2 つのインスタンスが実行されていることがわかります。

aws ecs describe-clusters --clusters ecs-bottlerocket --region us-west-2

{
    "clusters": [
        {
            "clusterArn": "arn:aws:ecs:us-west-2:123456789012:cluster/ecs-bottlerocket",
            "clusterName": "ecs-bottlerocket",
            "status": "ACTIVE",
            "registeredContainerInstancesCount": 2,
            "runningTasksCount": 0,
            "pendingTasksCount": 0,
            "activeServicesCount": 0,
            "statistics": [],
            "tags": [],
            "settings": [
                {
                    "name": "containerInsights",
                    "value": "disabled"
                }
            ],
            "capacityProviders": [],
            "defaultCapacityProviderStrategy": []
        }
    ],
    "failures": []
}

コンテナインスタンスを確認し、Bottlerocket が実行されているかどうかを調べてみましょう。以下のコマンド実行結果では 2 実行されていることがわかります。

aws ecs list-container-instances --cluster ecs-bottlerocket --region us-west-2

{
    "containerInstanceArns": [
        "arn:aws:ecs:us-west-2:123456789012:container-instance/ecs-bottlerocket/4061b1612a8b479695c638e9890136f4",
        "arn:aws:ecs:us-west-2:123456789012:container-instance/ecs-bottlerocket/8e06130837cf445aab0a79a82a363894"
    ]
}
    
aws ecs describe-container-instances \
   --region us-west-2 \
   --container-instances 4061b1612a8b479695c638e9890136f4 8e06130837cf445aab0a79a82a363894  \
   --cluster ecs-bottlerocket \
   --query 'containerInstances[].attributes[?name ==`bottlerocket.variant`]'

[
    [
        {
            "name": "bottlerocket.variant",
            "value": "aws-ecs-1"
        }
    ],
    [
        {
            "name": "bottlerocket.variant",
            "value": "aws-ecs-1"
        }
    ]
]

インスタンスの属性に、name が bottlerocket-variant、value が aws-ecs-1 の値があることがわかります。これは前述の Bottlerocket のバリアントで、ECS のために最小限のパッケージで特別にカスタマイズされています。

次に、サンプルアプリケーションのためのタスク定義を作成します。

cat > ./sample-task.json << 'EOF'
{
    "family": "ecs-bottlerocket-sample", 
    "networkMode": "awsvpc", 
    "containerDefinitions": [
        {
            "name": "sample-app", 
            "image": "httpd:2.4", 
            "portMappings": [
                {
                    "containerPort": 80, 
                    "hostPort": 80, 
                    "protocol": "tcp"
                }
            ], 
            "essential": true, 
            "entryPoint": [
                "sh",
        "-c"
            ], 
            "command": [
                "/bin/sh -c \"echo '<html> <head> <title>Amazon ECS Sample App</title> <style>body {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center> <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application is now running on a container in Amazon ECS.</p> </div></body></html>' >  /usr/local/apache2/htdocs/index.html && httpd-foreground\""
            ]
        }
    ], 
    "requiresCompatibilities": [
        "EC2"
    ], 
    "cpu": "256", 
    "memory": "512"
}
EOF

ECS にタスクを登録します。

aws ecs register-task-definition \
   --region us-west-2 \
   --cli-input-json file://sample-task.json

ECS クラスターにサービスを作成します。

aws ecs create-service --cluster ecs-bottlerocket \
   --region us-west-2 \
   --service-name bottlerocket-service \
   --task-definition ecs-bottlerocket-sample:1 \
   --desired-count 4 --launch-type "EC2" \
   --network-configuration "awsvpcConfiguration={subnets=[subnet-bc8993e6,subnet-b55f6bfe]}"

最後にサービスのステータスを確認します。4 つの実行中のタスクが表示されます。

aws ecs describe-services --cluster ecs-bottlerocket \
   --service bottlerocket-service \
   --region us-west-2 \
   --query 'services[].deployments'
   
[
    [
        {
            "id": "ecs-svc/9699136747523902404",
            "status": "PRIMARY",
            "taskDefinition": "arn:aws:ecs:us-west-2:123456789012:task-definition/ecs-bottlerocket-sample:1",
            "desiredCount": 4,
            "pendingCount": 0,
            "runningCount": 4,
            "failedTasks": 0,
            "createdAt": "2021-06-30T11:26:16.405000+03:00",
            "updatedAt": "2021-06-30T11:26:46.746000+03:00",
            "launchType": "EC2",
            "networkConfiguration": {
                "awsvpcConfiguration": {
                    "subnets": [
                        "subnet-bc8993e6",
                        "subnet-b55f6bfe"
                    ],
                    "securityGroups": [],
                    "assignPublicIp": "DISABLED"
                }
            },
            "rolloutState": "COMPLETED",
            "rolloutStateReason": "ECS deployment ecs-svc/9699136747523902404 completed."
        }
    ]
]

後片付け

この記事で作成したリソースを削除するには、次のコマンドを実行します。

aws ecs update-service --cluster ecs-bottlerocket \
   --region us-west-2 \
   --service bottlerocket-service \
   --desired-count 0
  
aws ecs delete-service --cluster ecs-bottlerocket \
   --region us-west-2 \
   --service bottlerocket-service
  
aws ecs deregister-task-definition \
   --region us-west-2 \
   --task-definition ecs-bottlerocket-sample:1
   
delete_instances=$(aws ec2 describe-instances --region us-west-2 \
   --filters "Name=tag-key,Values=bottlerocket" "Name=tag-value,Values=quickstart" \
   --query 'Reservations[].Instances[].InstanceId')  
   
for instance in $delete_instances
  do aws ec2 terminate-instances --instance-ids $instance --region us-west-2
done 

aws ecs delete-cluster \
   --region us-west-2 \
   --cluster ecs-bottlerocket

まとめ

この記事では Bottlerocket とは何か、どのように機能するのか、そして基盤となる OS と直接やり取りする方法について説明しました。また Bottlerocket を基盤とする OS 上で ECS のサービスを展開しました。ご覧の通り、ECS のタスクやサービスを Bottlerocket にデプロイする方法は他の OS と変わりません。

Bottlerocket のコンポーネントはオープンソースです。Bottlerocket はコラボレーション型のコミュニティプロジェクトとなることを目的としているため、直接コントリビュートし、独自のカスタマイズバージョンも作成できます。みなさまが Bottlerocket に関わり、フィードバックを提供することを歓迎したいと思います。GitHub リポジトリをチェックして、Issue を介した議論やプルリクエストを介したコントリビューションをご覧ください。AWS Developer Slack では、非公式なやり取りのための #bottlerocket チャネルもあります。こちらからサインアップできます

翻訳はソリューションアーキテクト加治が担当しました。原文はこちらです。