AWS FargateからOCI CLIを実行できるようにした
経緯
OCI ObjectStorageに蓄積されたデータ(具体的には監査ログ)をAthenaで分析するためログファイルをS3に持ってくる必要があり方法を検討しました。
自身の知見や利用できる周辺サービスが多いため、処理を実行するのはAWS側で決定していたのですが、LambdaとFargate、PythonとShellScript、OCI CLIとS3互換APIなど利用技術の選択肢がいくつかありました。
以下のような点から最終的にFargateでOCI CLIとAWS CLIを実行することにしました。
- Lambdaで使えるストレージが512MBのためログをダウンロードする時の容量に不安があるがFargate(1.4.0)であれば20GB使える。
- OCI、AWS共に各種SDKではbulk-downloadやs3 syncのような高レベルコマンドが実行できない。
- OCI ObjectStorageがS3互換APIを備えているがサポートされるAPIは一部に限られている。
OCI認証情報の扱い
OCI CLIを実行するためにはconfigファイルと秘密鍵が必要です。
SDKおよびCLIの構成ファイル
Dcoker imageの中に認証情報を直接埋め込みたくなかったので、configファイルと秘密鍵はAWS Secrets Managerにバイナリデータとして保存し、Fargateタスク起動時に取得するようにしました。Terraformでバイナリデータのシークレットを作成する場合次のようになります。
# config
resource "aws_secretsmanager_secret" "oci-config" {
name = "oci-config"
}
resource "aws_secretsmanager_secret_version" "oci-config" {
secret_id = aws_secretsmanager_secret.oci-config.id
secret_binary = filebase64("docker/oci-cli/.oci/config")
}
# 秘密鍵
resource "aws_secretsmanager_secret" "oci-apikey" {
name = "oci-apikey"
}
resource "aws_secretsmanager_secret_version" "oci-apikey" {
secret_id = aws_secretsmanager_secret.oci-apikey.id
secret_binary = filebase64("docker/oci-cli/.oci/oci_api_key.pem")
}
FargateにはタスクロールでSecrets Managerの権限を渡します。
data "aws_iam_policy_document" "ecs-tasks-assume-role-policy" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role" "ecs-task-role" {
name = "ecs-task-role"
assume_role_policy = data.aws_iam_policy_document.ecs-tasks-assume-role-policy.json
}
data "aws_iam_policy_document" "ecs-task-secretsmanager" {
statement {
effect = "Allow"
actions = [
"secretsmanager:GetSecretValue",
]
resources = [
aws_secretsmanager_secret.oci-config.arn,
aws_secretsmanager_secret.oci-apikey.arn
]
}
}
resource "aws_iam_policy" "ecs-task-secretsmanager" {
name = "ecs-task-secretsmanager"
policy = data.aws_iam_policy_document.ecs-task-secretsmanager.json
}
resource "aws_iam_role_policy_attachment" "ecs-task-role_ecs-task-secretsmanager" {
role = aws_iam_role.ecs-task-role.name
policy_arn = aws_iam_policy.ecs-task-secretsmanager.arn
}
なおOCI環境上でOCI CLIやSDKを実行する場合はインスタンスプリンシパルというAWSのIAM Roleのような機能で動的に認証情報を渡すこともできます。
そのほかのリソース
ECSとECRの設定をします。
resource "aws_ecr_repository" "oci-cli" {
name = oci-cli
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
resource "aws_ecs_cluster" "oci-cli" {
name = "oci-cli-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
resource "aws_ecs_task_definition" "oci-cli" {
family = oci-cli
container_definitions = templatefile(
"ecs/task-definition/oci-cli.json",
{
region = var.aws_region
image = "${aws_ecr_repository.oci-cli.repository_url}:latest"
name = oci-cli
compartment = var.compartment
ociconfig = aws_secretsmanager_secret.oci-config.name
ociapikey = aws_secretsmanager_secret.oci-apikey.name
}
)
execution_role_arn = "arn:aws:iam::${data.aws_caller_identity.self.account_id}:role/ecsTaskExecutionRole"
task_role_arn = aws_iam_role.ecs-task-role.arn
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = 256
memory = 512
}
タスク定義の設定で読み込んでいるテンプレートです。
[
{
"logConfiguration": {
"logDriver": "awslogs",
"secretOptions": null,
"options": {
"awslogs-group": "/ecs/${name}",
"awslogs-region": "${region}",
"awslogs-stream-prefix": "ecs"
}
},
"name": "${name}",
"image": "${image}",
"essential": true,
"environment": [
{
"name": "COMPARTMENT",
"value": "${compartment}"
},
{
"name": "OCICONFIG",
"value": "${ociconfig}"
},
{
"name": "OCIAPIKEY",
"value": "${ociapikey}"
}
]
}
]
OCI CLIとAWS CLIをインストールするDockerfileです。一応マルチステージビルドのようなことをやっていますがまだ改良の余地がある気がしています。
# AL2 image Tag
ARG AL2Tag="2.0.20200722.0"
# build
FROM amazonlinux:${AL2Tag} AS builder
COPY requirements.txt /root
RUN yum install -y unzip python3-pip && \
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
./aws/install --bin-dir /aws-cli-bin/ && \
pip3 install -r /root/requirements.txt
# deploy
FROM amazonlinux:${AL2Tag}
COPY /usr/local/aws-cli/ /usr/local/aws-cli/
COPY /aws-cli-bin/ /usr/local/bin/
COPY /root/.cache/pip /root/.cache/pip
COPY /root/requirements.txt /root/requirements.txt
RUN yum update -y && \
yum install -y less groff jq python3-pip && \
yum clean all && \
pip3 install -r /root/requirements.txt && \
rm -rf /root/.cache/pip
RUN mkdir /root/.oci && \
mkdir /usr/local/oci-cli
COPY oci-cli.sh /usr/local/oci-cli/
ENTRYPOINT ["/usr/local/oci-cli/oci-cli.sh"]
だいぶ簡略化していますが、Fargateで実行するスクリプトはこんな感じです。
#!/bin/bash
# 環境変数からコンパートメントIDやシークレット名を取得
readonly compartment=$COMPARTMENT
readonly ociconfig=$OCICONFIG
readonly ociapikey=$OCIAPIKEY
readonly region="ap-tokyo-1"
# configファイルと秘密鍵の取得
aws secretsmanager get-secret-value \
--secret-id "$ociconfig" \
| jq -r .SecretBinary \
| base64 --decode > /root/.oci/config
aws secretsmanager get-secret-value \
--secret-id "$ociapikey" \
| jq -r .SecretBinary \
| base64 --decode > /root/.oci/oci_api_key.pem
chmod 400 /root/.oci/config
chmod 400 /root/.oci/oci_api_key.pem
# Object Storageのバケット一覧
oci os bucket list \
--compartment-id "$compartment" \
--all \
--region "$region"
実際の環境ではタスクロールにS3の権限を割り当て、 oci os object bulk-download
aws s3 sync
といったコマンドを実行するスクリプトを配備してObjectStorageからS3へのファイル同期を行なっています。
余談:OCIとS3のバケット名
S3はグローバル(全てのAWSアカウント)で一意な名前をつける必要がありますが、ObjectStorageではOCIアカウント内(実際には割り当てられるネームスペース)のリージョン内で一意であれば良いようです。
オブジェクト・ストレージ・ネームスペースの理解から引用します。
オブジェクト・ストレージ・ネームスペースは、すべてのバケットおよびオブジェクトのコンテナとして機能します。ネームスペース内でバケット名を制御しますが、バケット名は各リージョン内で一意である必要があります。米国西部(フェニックス)にMyBucketという名前のバケットと、ドイツ中央部(フランクフルト)にMyBucketという名前のバケットを持つことができます。
バケット名に関してはOCIの方が柔軟性がありそうでした。
Discussion