背景
少し前にAWS Elastic BeanstalkからECS On Fargate(以下、Fargate)への移行を行いました。この移行に際し、注意すべき非機能要件がいくつかありましたので、それについて記載します。
ロギング
ECSのコンテナは必要に応じて起動・削除されるため、コンテナ内にログを蓄積しておくと削除されてしまいます。そのため、ログドライバーを利用して外部にログを転送する必要があります。
ECSのロギングの設定には2つのパターンがあります。
CloudWatchLogsにログを送信するawslogsログドライバーを利用するパターン
CloudWatchLogsにログを送信する形式です。
送信先はCloudWatchLogsしか選択できないため、コスト面が気になる場合やCloudWatchLogs以外のサービスにログを流したい場合などは後続のawsfirelensログドライバーを選択した方が良いでしょう。
こちらのメリットとしては、ログドライバーを設定するだけで済むので設定が簡単です。
自分が担当しているサービスはCloudWatchLogsにサービスのログを集約する運用だったため、こちらのロギングを選択しました。
taskDefinition.addContainer("NginxContainer", {
image: ecs.ContainerImage.fromDockerImageAsset(nginxImageAsset),
//ログドライバー設定
logging: ecs.LogDrivers.awsLogs({
logGroup,
streamPrefix: `nginx`,
}),
portMappings: [
{
containerPort: 80,
hostPort: 80,
},
],
readonlyRootFilesystem: true,
});
- CloudWatchLogs以外にログを送信したい場合のawsfirelensログドライバーを利用するパターン
FireLensを使い、指定したサービスにログを送信します。自分が担当したサービスではこちらの設定を行いませんでしたが、検討した際に下記ページに詳しく記載があったため紹介させていただきます。
RootファイルのReadOnly設定
コンテナ内への攻撃者の侵入を許してしまった場合、コンテナ内のルートファイルへの書き込みが可能な状態では、被害が拡大する恐れがあります。そのため、アプリケーション上の制約がなければ可能な限りRootファイルのReadOnly設定を有効にしておくことをお勧めします。
taskDefinition.addContainer("NginxContainer", {
image: ecs.ContainerImage.fromDockerImageAsset(nginxImageAsset),
logging: ecs.LogDrivers.awsLogs({
logGroup,
streamPrefix: `nginx`,
}),
portMappings: [
{
containerPort: 80,
hostPort: 80,
},
],
//RootファイルのReadOnly設定
readonlyRootFilesystem: true,
});
スケーリング設定
Fargateのスケーリング設定はサービス構成の中で設定します。
AutoScalingを設定し、maxおよびmin capacityを設定することで、タスクの必要数を増減させることができます。
可用性を向上させるためにも、こちらの設定は入れておく必要があります。
// Auto Scaling設定
const scaling = service.autoScaleTaskCount({
minCapacity,
maxCapacity,
});
scaling.scaleOnRequestCount("RequestScaling", {
requestsPerTarget: 100,
targetGroup,
});
ECRのイメージスキャン
ECSを利用する際にはECRにイメージをプッシュするのが一般的ですが、ECRにはプッシュされたイメージをスキャンして、コンテナイメージ内のソフトウェアの脆弱性を特定してくれる機能があります。スキャンにはベーシックスキャンと拡張スキャンの2種類があります。
- ベーシックスキャン
ベーシックスキャンでは、プッシュ時にスキャンするようにリポジトリを設定します。
手動スキャンを実行するとAmazon ECRによってスキャン結果のリストが提供されます。また、スキャンは24時間に1回スキャンできます。
- 拡張スキャン
ECRはAmazon Inspectorと統合され、リポジトリの自動継続的なスキャンを提供します。コンテナイメージは、オペレーティングシステムとプログラミング言語パッケージの両方の脆弱性についてスキャンされます。新しい脆弱性が発生すると、スキャン結果が更新され、Amazon InspectorはEventBridgeにイベントを発行して通知します。
下記はベーシックスキャンの設定をcdkで行った例です。
const serviceRepository = new ecr.Repository(this, "ServiceRepository", {
repositoryName: `service-repo`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
emptyOnDelete: true,
imageScanOnPush: true,
});
ECRのイメージスキャンの注意点
一般的なCI/CDでは、イメージプッシュ前にスキャンを実施し、デプロイを許可しないようにしていることが多いです。
ECRのイメージスキャンの場合、スキャン実行は非同期で行われるため、イメージビルド時に脆弱性があってもイメージプッシュを止められません。そのため、下記のような運用や対策が必要になります。
- ECRのスキャン結果を通知する
開発環境などで本番環境デプロイ前に脆弱性の検知を拾える仕組みを作ります。弊社ではECRのスキャン結果をSlackに通知することで、異常検知された場合、本番環境へのデプロイを停止する運用をしています。
interface StackProps extends cdk.StackProps {
readonly accountName: string;
readonly infoEmail: string;
}
export class InfoStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
const { accountName, infoEmail } = props;
const topic = new Topic(this, "Topic", {
displayName: `${accountName}-info`,
topicName: `${accountName}-info-topic`,
});
topic.addToResourcePolicy(
new PolicyStatement({
actions: ["sns:Publish"],
effect: Effect.ALLOW,
principals: [new AnyPrincipal()],
resources: ["*"],
})
);
topic.addSubscription(new EmailSubscription(infoEmail));
new StringParameter(this, "ParameterInfoTopicArn", {
parameterName: `/account/info-topic-arn`,
stringValue: topic.topicArn,
});
// ECR Image Scan Info
new Rule(this, "EcrImageScanRule", {
ruleName: `${accountName}-ecr-image-scan-rule`,
eventPattern: {
source: ["aws.ecr"],
detailType: ["ECR Image Scan"],
detail: {
"scan-status": ["COMPLETE"],
},
},
targets: [new SnsTopic(topic)],
});
}
}
GuardDuty ECS ランタイムモニタリング
GuardDutyのECSランタイムモニタリングを有効にすることで、現在稼働しているECSクラスターの脅威検知をしてくれます。検知できる内容としては下記があります。
- 不審なアクティビティのプロセスIDの把握
- 脆弱性があるコンテナイメージの特定
ECSランタイムモニタリングを有効にすると、ECSのタスクにaws-guardduty-agentというコンテナが起動されるようになります。
また、GuardDutyのランタイムモニタリングからランタイムカバレッジを参照すると、モニタリングしているクラスターリストが確認できます
GuardDuty ECS ランタイムモニタリングの導入時の注意点
ECSランタイムモニタリングを有効にすると、ECSのタスクにaws-guardduty-agentのコンテナが追加されます。そのため、タスクの実行ロールにGuardDutyエージェントのリポジトリに対するイメージのPull権限が必要です。対象のECSサービスのタスクがVPC内のプライベートサブネットにある場合、ssmmessagesのVPCエンドポイントが必要になります。
const executionRole = new iam.Role(this, "ExecutionRole", {
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonECSTaskExecutionRolePolicy")],
inlinePolicies: {
CustomPolicy: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: ["ecr:ListImages", "ecr:DescribeImages"],
resources: [`arn:*:ecr:*:*:repository/aws-guardduty-agent-fargate`],
effect: iam.Effect.ALLOW,
}),
],
}),
},
});
ECS Exec
ECS ExecはECSコンテナ内にSystems Manager Session Managerの機能を利用してログインする機能です。アプリケーションやコンテナ内部での問題が発生した場合、調査や復旧の目的で利用します。
ECS Execを利用するには、主に下記の設定が必要です。
-
ECSのタスクロールに権限を追加
policy{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssmmessages:CreateControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenControlChannel", "ssmmessages:OpenDataChannel" ], "Resource": "*" } ] }
-
ECS Execの有効化
下記はcdkでの有効化設定の例です。サービスの設定でECS Execを有効化する必要があります。
cdkconst service = new ecs.FargateService(this, "Service", { cluster, taskDefinition, desiredCount: minCapacity, securityGroups: [ecsSecurityGroup], vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, circuitBreaker: { rollback: true, }, enableExecuteCommand: true, // ECS Execの有効化 });
-
VPC Endpoint
対象のECSサービスのタスクがVPC内のプライベートサブネットにある場合、ssmmessages
のVPCエンドポイントが必要です。
参考