はじめに
コンテナ化したアプリを動かす際に、ファイル出力をしたいというニーズをちょくちょく聞きます。特に既存のアプリケーションに対して、なんとかコンテナ化まではできたが、ファイル出力する仕様までは変更することができなく、どうしたら良いかという話をよく耳にします。ここではその対処方法について記します。
Amazon Elastic Container Serviceでの対処方法
以下の公開情報にAmazon ECS上で取れるストレージサービスとその特性がまとまっています。
多くの場合、永続化したいというニーズかと思いますので、Amazon EFSを使うのが選択になるかなと考えています。Windowsで動かしている場合はAmazon FSx for Windows File ServerかDockerボリュームが選択肢になるかと思いますが、起動タイプがEC2しかダメなのがちょっと難点です。
App Runner
お手軽にコンテナ化したアプリを動かす環境としてApp Runnerがあります。Amazon ECSは動かすために色々とリソースを作る必要がありちょっとハードルが高いので、App Runnerを使いたいこともままあるのですが、現時点ではApp RunnerはAmazon EFSなどに永続化の目的で出力させることはできないようです。
GitHubのIssueとしてかなり若い番号で裁判された要望があるのですが、まだOpenのままでした。
実装
というわけで、Amazon ECS上でAmazon EFSをマウントすることをCDKを使って実装してみました。
import { Vpc } from 'aws-cdk-lib/aws-ec2';
import { Cluster, ContainerDefinition, ContainerImage, FargateTaskDefinition } from 'aws-cdk-lib/aws-ecs';
import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns';
import { FileSystem, LifecyclePolicy, PerformanceMode, ThroughputMode } from 'aws-cdk-lib/aws-efs';
import { AnyPrincipal, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
export class EcsWithEfsStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const vpc = new Vpc(this, 'MyVPC', { maxAzs: 2});
const ecsCluster = new Cluster(this, 'EcsCluster', { vpc: vpc });
const fileSystem = new FileSystem(this, 'MyEfsFileSystem', {
vpc: vpc,
encrypted: true,
// テスト目的なので、removalPolicyは削除とする
removalPolicy: RemovalPolicy.DESTROY,
lifecyclePolicy: LifecyclePolicy.AFTER_14_DAYS,
performanceMode: PerformanceMode.GENERAL_PURPOSE,
throughputMode: ThroughputMode.BURSTING
});
fileSystem.addToResourcePolicy(
new PolicyStatement({
actions: ['elasticfilesystem:ClientMount'],
principals: [new AnyPrincipal()],
conditions: {
Bool: {
'elasticfilesystem:AccessedViaMountTarget': 'true'
}
}
})
);
const taskDef = new FargateTaskDefinition(this, "MyTaskDef", {
memoryLimitMiB: 512,
cpu: 256,
volumes: [
{
name: "hogehoge",
efsVolumeConfiguration: {
fileSystemId: fileSystem.fileSystemId,
}
}
]
});
const containerDef = new ContainerDefinition(this, 'MyContainerDefinition', {
image: ContainerImage.fromRegistry('コンテナイメージ名'),
taskDefinition: taskDef
});
containerDef.addMountPoints(
{
containerPath: '/mydata',
sourceVolume: 'mydata',
readOnly: false
}
);
containerDef.addPortMappings({
containerPort: ポート番号,
});
const albFargateService = new ApplicationLoadBalancedFargateService(this, 'MyALBService', {
cluster: ecsCluster,
taskDefinition: taskDef,
desiredCount: 1
});
albFargateService.targetGroup.setAttribute('deregistration_delay.timeout_seconds', '30');
fileSystem.grantRootAccess(albFargateService.taskDefinition.taskRole.grantPrincipal);
fileSystem.connections.allowDefaultPortFrom(albFargateService.service.connections);
}
}
ポイントは、タスク定義のvolumes以下の部分です。
const taskDef = new FargateTaskDefinition(this, "MyTaskDef", {
memoryLimitMiB: 512,
cpu: 256,
volumes: [
{
name: "hogehoge",
efsVolumeConfiguration: {
fileSystemId: fileSystem.fileSystemId,
}
}
]
});
考察
まずはAmazon EFSをマウントするECSを実装してみました。複数の選択肢がありますが、永続化という目的であればAmazon EFSが有力候補になるかと思います。
実際にはAmazon EFSをECSにマウントしただけではよくなく、中身を参照したりするニーズがあります。そのニーズを満たすために以下のハンズオンが参考になりそうです。
このハンズオンを元にしたより実践的なCDK実装を実装していこうかと考えています。