0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS CDK(TS)でECSにEFSをmountさせるのにめちゃはまったのでメモとして残しておく

Posted at

AWS CDK で Qdrant を ECS にデプロイし、EFS をマウントしたときのメモ

AWS CDK を使って Qdrant(ベクターデータベース)を Fargate/ECS 上で動かしつつ、データの永続化に EFS を使おうとした際に色々とはまったので、その時のメモです。
特に以下の記事にある Tips がとても参考になり、助かりました。

ChatGPT などの LLM に聞きながら進めていたら、逆に色々な設定を試しすぎてしまい、不要なものまで入っている可能性があります。そのため、このコードは「たぶんいらないものもあるけれど、一応動いた形」という前提でご参照ください。


やりたいこと

  • Qdrant というコンテナイメージを Fargate/ECS にデプロイ
  • 保存したベクターやメタデータを EFS に永続化
  • Fargate タスクから EFS をマウントし、/qdrant/storage ディレクトリとして使う

はまったポイント

  1. FileSystem の availabilityZones 設定の付け忘れ
    • EFS をプライベートサブネットに作成する際、vpcSubnetsavailabilityZones を指定しないとデプロイでエラーになることがありました。
  2. FileSystem の grantReadWrite 設定の付け忘れ
    • ECS タスクロールなど、書き込みを行うロールに明示的に EFS へのアクセス権を付与していなかったためアクセス拒否。
  3. ECS タスクの authorizationConfigiam: true を設定し忘れ
    • iam: 'ENABLED' にしないと IAM による認可が働かず、EFS のマウントが失敗。

実際の CDK コード例

以下に今回動いたコードをそのまま載せます。
(※すでに述べたように、余計なものや不要なものが混ざっているかもしれませんが、必要最低限の実装部分も含まれているはずです。)

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as efs from 'aws-cdk-lib/aws-efs';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as ecr from 'aws-cdk-lib/aws-ecr';

interface MyStackProps extends cdk.StackProps {
  stackName: string;
  qdrantSG: ec2.SecurityGroup;
}

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MyStackProps) {
    super(scope, id, props);

    // (VPC を新規に作成する場合)
    const vpc = new ec2.Vpc(this, "VPC", {
      availabilityZones: ['ap-northeast-1a', 'ap-northeast-1c'],
      natGateways: 1,
      subnetConfiguration: [
        {
          name: 'Public',
          subnetType: ec2.SubnetType.PUBLIC,
          cidrMask: 24
        },
        {
          name: 'Private',
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
          cidrMask: 24
        }
      ],
      enableDnsHostnames: true,
      enableDnsSupport: true
    });

    // ECS Task の実行ロール
    const taskExecutionRole = new iam.Role(this, "ecsTaskExecutionRole", {
      roleName: `${props.stackName}-ecsTaskExecutionRole`,
      assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com")
    });

    // タスク実行ロールに ECR 関連のアクセス権限を付与
    taskExecutionRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: [
          "ecr:GetAuthorizationToken",
          "ecr:BatchCheckLayerAvailability",
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage"
        ],
        resources: ["*"]
      })
    );
    
    // タスク実行時の IAM ロール
    const taskRole = new iam.Role(this, "ecsTaskRole", {
      assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
      roleName: `${props.stackName}-ecsTaskRole`
    });

    // SSM / SecretsManager へのアクセス例
    taskRole.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore")
    );
    taskRole.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName("SecretsManagerReadWrite")
    );

    // EFS の作成
    const fileSystem = new efs.FileSystem(this, 'QdrantStorage', {
      vpc: vpc,
      lifecyclePolicy: efs.LifecyclePolicy.AFTER_14_DAYS,
      performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
        availabilityZones: vpc.availabilityZones
      }
    });

    // タスクロールに EFS への Read/Write 権限を付与
    fileSystem.grantReadWrite(taskRole);
    fileSystem.grantReadWrite(taskExecutionRole);

    // ECS の FargateTaskDefinition を作成
    const qdrantTaskDef = new ecs.FargateTaskDefinition(this, 'QdrantTaskDef', {
      memoryLimitMiB: 512,
      cpu: 256,
      taskRole: taskRole,
      executionRole: taskExecutionRole,
      family: `${props.stackName}-Qdrant`,
      volumes: [
        {
          name: 'qdrant_data',
          efsVolumeConfiguration: {
            fileSystemId: fileSystem.fileSystemId,
            transitEncryption: 'ENABLED',
            authorizationConfig: {
              iam: 'ENABLED'
            },
          }
        }
      ],
    });

    // Qdrant の ECR リポジトリを参照してコンテナを作成
    const qdrantRepo = ecr.Repository.fromRepositoryName(this, 'QdrantRepo', 'qdrant');
    const qdrantContainer = qdrantTaskDef.addContainer('QdrantContainer', {
      image: ecs.ContainerImage.fromRegistry('qdrant/qdrant:v1.8.1'),
      portMappings: [{ containerPort: 6333 }],
      environment: {
        TZ: "Asia/Tokyo"
      },
    });

    // コンテナ内の /qdrant/storage を EFS ボリュームとマウント
    qdrantContainer.addMountPoints({
      sourceVolume: 'qdrant_data',
      containerPath: '/qdrant/storage',
      readOnly: false
    });

    // サービスを作成
    // 例としてすでに作成済みの ECS クラスターを渡している想定
    const cluster = ecs.Cluster.fromClusterAttributes(this, 'ImportedCluster', {
      clusterName: 'MyCluster',
      vpc: vpc,
      securityGroups: [] // 必要に応じて
    });

    // Service 作成
    const qdrantService = new ecs.FargateService(this, 'QdrantService', {
      serviceName: `${props.stackName}-Qdrant`,
      cluster,
      taskDefinition: qdrantTaskDef,
      desiredCount: 1,
      assignPublicIp: false,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
      securityGroups: [props.qdrantSG],
      cloudMapOptions: {
        name: 'qdrant',
        cloudMapNamespace: {
          // こちらも別途作成・参照する必要あり
          namespaceArn: 'arn:aws:servicediscovery:xxx:namespace/xxx',
          namespaceId: 'xxx',
          type: ecs.CloudMapNamespaceType.HTTP
        }
      },
      platformVersion: ecs.FargatePlatformVersion.LATEST,
    });

    // EFS のデフォルトポート(2049)を、Qdrant Service から許可
    fileSystem.connections.allowDefaultPortFrom(qdrantService, "Allow Qdrant to access EFS");
  }
}

まとめ

  • EFS を ECS タスクにマウントする際は、FileSystem 側の設定ECS タスクのボリューム設定の両方をしっかり行う必要があります。
  • 特に iam: 'ENABLED' を入れないといけない点と、grantReadWrite で適切に権限を付与しないとアクセス拒否が起きる点は要注意。
  • 今回は こちらの記事 が非常に参考になりました。
  • LLM (ChatGPT) で答えをもらおうとすると、様々なサンプルコードが降ってきて、かえって設定が複雑になりがちなので、公式ドキュメントや信頼できる記事をしっかり読んで設定を確認するのがやはり大事と感じました。

「とりあえず動く」実装にはなったものの、まだ不要なものが含まれている可能性がありますが、何かの参考になれば幸いです。もし整理してもっとスマートな設定にした方が良い場合は、この記事を参考にしつつ適宜削ぎ落としてみてください。


以上、AWS CDK を使って Qdrant + ECS + EFS を構成するときのメモでした。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?