AWS管理者の方で、開発者に対してAWS上にユーザー、開発環境、デプロイ先のフルセットをすぐ提供できたらな...と思うことはないでしょうか。
この思いを解決すべく、このフルセットをTerraformで書いてみました。同じ思いの方がいましたら、ぜひ使ってみてください🙏
構成
下記のような環境を簡単にいくつでもTerraformを使って払い出すことができます。青矢印がTerraform、オレンジ矢印が開発者の利用の流れです。
先に構築しておくもの
- SageMaker AIドメイン
- 共通IAM
- ネットワーク
- 共通ECS
開発者単位で払い出すもの
- IAMユーザー
- SageMaker AIプロファイル(開発環境)
- ECR リポジトリ
- 個別ECS
Git関連
開発者用のGitリポジトリは手動で複製が必要です。簡単なCI/CDも用意しましたが、不要であればVSCode上でビルドし、ECRへ直接pushしていただくという方法でももちろんOKです。
Terraform
コードはすべて載せると長くなるので、主要な箇所のみ記載します。ディレクトリ構成は、開発者単位でstateを持つ設計にしています。
infraは一度applyすればOKで、あとは開発者が増えるごとにtemplate-developerをコピーしてapplyします。
terraform-aws-developer-environment/
├── developers/
│ ├── modules/
│ │ └── developer/
│ │ └── main.tf
│ └── template-developer/
│ └── main.tf
└── infra/
├── ecs.tf
├── iam.tf
└── network.tf
構築
ではここから実際に構築していきます。
本環境は権限やネットワークなど、セキュリティの考慮が最小限の構成になっています。セキュリティを意識する場合に必要な設定は後述するので、必要であれば適宜適用してください。
GitHub
コードは全部のせると長くなるので、Qiitaには重要な部分のみの掲載とします。全体のコードは下記リポジトリをご利用ください。コードはすべて動作を確認しています。
Terraform
providerにprofileを仕込んでいるので、適宜ご利用ください。
開発者のテンプレートリポジトリ
nginxのテストイメージに加え、ActionsのworkflowsとECSのタスク定義が配置されています。開発者はこれをコピーして自分用にカスタマイズするイメージです。
インフラ(infra)
まずはインフラ面から作ります。SageMaker AIのドメインと、Terraformのinfra配下でapplyしてもらえれば環境は作れますが、それぞれどんなことをしているか解説します。
SageMaker AIドメイン
今回は手動で「クイックセットアップ」から作成します。「組織向けの設定」であれば、自分で作ったVPCを使ったりもできるので、必要に応じて使い分けてください。
SageMaker AI → ドメイン → ドメイン作成 → SageMaker Domain を設定 → シングルユーザー向けの設定 (クイックセットアップ)
IAM
作るのは開発者用のIAMグループと、ECSロール、SageMaker AIロールです。この環境では権限はゆるくしてるので共通で作りました。自分の環境だけに対して権限を与えるということもできるので、必要なら適宜行ってください。SageMaker AIはプロファイルとIAMユーザーを紐付けて、特定のIAMユーザーしか使えないようにしたりすることもできます。
ネットワーク
VPC、サブネット、ALBを作ります。今回はすべてパブリックサブネットに配置しています。セキュアにするならプライベートサブネットを作成して、そこにECSを配置、NATを作る必要があります。
またこのシステムではドメインをとっていないので、https化もしていません。必要ならドメインをとって、https化を行なってください。
ALBはパスが存在しないアクセスを404にしています。
# ALB リスナー (HTTP)
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404 Not Found"
status_code = "404"
}
}
}
ALBは払い出しのサービス共通で利用します。サービスが大量に増えたり、トラフィックが危なくなってきたら、追加を検討してください。
ECS
デプロイ直後のベースイメージとクラスター、共通SGを作っています。
outputs.tf
開発環境のディレクトリでremote stateを使うので、受け渡しが必要なリソースをoutputしておきます。長いので一部だけ記載します。
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "パブリックサブネットのIDリスト"
value = [aws_subnet.public_1.id, aws_subnet.public_2.id]
}
...
ECRへのイメージ配置
ここまででインフラ環境のデプロイができたら、ECRにベースのイメージを配置しておきましょう。とくにこだわりがなければ、infra/base-imageにbase用のnginxイメージと、push用のコマンドを配置しているので、これをベースのリポジトリにpushしておきます。
開発環境(developers)
次は開発環境です。modulesから解説します。それほど大きなコードでもないので、簡単にmodulesが取り出せることを優先し、developerに集約しました。
開発環境はtemplate-developerディレクトリをコピーして、tfvarsを編集してapplyすれば、デプロイすることができます。
IAM
IAMユーザーを作ります。IAMユーザーはパスワードを確認する必要があるので、modulesとその取り出し先の両方でoutputs.tfを書きます。取り出し元にはnonsensitiveをつけてください。
output "iam_user_password" {
description = "IAMユーザーのパスワード"
value = nonsensitive(aws_iam_user_login_profile.developer.password)
}
本コードではstateにもパスワードが出力されますが、.gitignoreで回避しています。stateをS3で管理する場合は、アクセス制限を確実にしておきましょう。もしくは別途対策を検討してください。
SageMaker AIプロファイル
開発環境はこのSageMaker AIのプロファイルからCode Editorのスペースを使います。
ECS
ALBのリスナールール、ターゲットグループとECSサービスを作ります。
リスナールールは開発者がパスを決められるように変数にしています。
# ALB リスナールール
resource "aws_lb_listener_rule" "developer" {
listener_arn = var.alb_listener_arn
priority = var.listener_priority
condition {
path_pattern {
values = [
"/${var.path_name}/*"
]
}
}
action {
type = "forward"
target_group_arn = aws_lb_target_group.developer.arn
}
}
template-developer(modulesの取り出し先)
開発環境の追加はこのディレクトリをコピーして作っていきます。
remote state
前述の通りremote stateを使うので、dataブロックを作っています。
# リモートステート
data "terraform_remote_state" "infra" {
backend = "local"
config = {
path = "../../infra/terraform.tfstate"
}
}
tfvars
開発者固有の変数です。sagemaker_domain_id以外は一意の文字列にしてください。
- sagemaker_domain_id:構築冒頭で作成したドメインから取得
- developer_name:開発者の名前
- path_name:ドメインの後ろにつくパスの文字列
- listener_priority:ALBリスナーの優先度
# 固定
sagemaker_domain_id = "XXX"
# ユーザーごとに変更
developer_name = "test-developer"
path_name = "test-system"
listener_priority = 100
※補足:よりセキュアな環境を目指す場合にやるべきこと
いくつか前述しましたが、改めてまとめておきます。必要なら実施してください。
- SageMaker DomainをVPC内で設定する
- ECSをプライベートサブネットに配置する(パブリックサブネットにNAT配置)
- ALBをドメインに紐づける(証明書でhttps化)
- ECSやSageMaker AIのIAMロールの対象を開発者のリソースに限定する
- IAMパスワードの対策
- 開発者のIAMに自分が使うサービスの権限を限定的に与える(下記のコードで可能)
CI/CD(GitHub Actions)
最後はCI/CDです。開発者のテンプレートリポジトリから利用できます。アプリに関してはnginxを用意していますが、こちらは自由にカスタマイズしてください。
workflows.yaml
今回は簡易的にpushでのみデプロイとしています。必要ならプルリクなどでも動作を加えてください。また、ユーザー名がユーザー自身で入力が必要なので、変数にしています。
...
on:
# pushでデプロイ
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
env:
# ユーザー名変数にしておく
DEVELOPER_NAME: test-developer
...
task-definition.json
タスク定義です。初回はTerraformで作りますが、以降は開発者が設定できるように、リポジトリに配置しています。基本はTerraform側と同じですが、アカウント番号やユーザー名を入力できるようにしています。
なお、ECSサービスの取得先はこのタスク定義をみて、baseからtemplateに切り替わります。
...
"containerDefinitions": [
{
"name": "app",
"image": "<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/devenv-test-developer-ecr:latest",
"cpu": 0,
...
開発環境を払い出してみる
では実際に開発環境を払い出してみます。事前にinfraのデプロイとbaseイメージの配置が、実施済みであることをご確認ください。開発環境払い出しの流れは以下です。
・環境のデプロイ
・リポジトリコピー / クレデンシャルの設定
・SageMaker AI(Code Editor)からのGit操作(CI/CDでのデプロイ)
環境のデプロイ
ディレクトリのコピー
下記のようにコピーしてください。
terraform-aws-developer-environment/
└── developers/
├── modules/
│ └── developer/
│ └── main.tf
├── template-developer/
│ └── main.tf
└── test-developer/ # コピーしたディレクトリ
└── main.tf
開発者を追加するときはコピーというアナログチックな方法を採用しています。
複数stateの管理でもWorkspacesやTerragruntを使えば、よりスマートな環境複製ができるかと思います。Workspacesであればtfvarsを作るだけ、Terragruntであればコピー自体は必要なものの、コードの記述を大きく減らすことができそうです。
tfvarsの編集
前述していますが、再度記述します。ここまでやればapplyしてOKです。
# 固定
sagemaker_domain_id = "XXX"
# ユーザーごとに変更
developer_name = "test-developer"
path_name = "test-system"
listener_priority = 200
apply後、サービスにアクセスしてみましょう。
Git リポジトリのコピー
Use this template → Create a new repository
リポジトリはもちろんこのリポジトリを使っても良いですが、アプリ部分を自由に編集いただいて、新たなテンプレートとして提供してもOKです。その場合、SettingsのTemplate repository にチェックをいれてください。
AWS アクセスキーの登録
AWSでIAMからアクセスキーを取得し、Git上に下記2つを登録してください。
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
Settings → secrets and variables → Actions → Repository secrets → New repository secrets
SageMaker AI / Code Editorの利用
ここからは実際に開発者が行う操作です。
SageMaker AI → 該当ドメイン → 自分のプロファイル → Code Editor
Code Editor → Create Code Editor space →(スペースができたら) Open
これで見慣れたVSCodeの画面が出てくるので、コピーしたGitリポジトリをcloneしましょう。
clone後の画面です。
git push(workflows.yamlとtask-definition.jsonの編集)
コード内にデプロイのための変数があるので、編集します。
workflows.yaml
DEVELOPER_NAMEをtfvarsのときに使ったユーザー名へ編集します。
...
jobs:
deploy:
runs-on: ubuntu-latest
env:
# ユーザー名を変数にしておく
DEVELOPER_NAME: test-developer
...
task-definition.json
タスク定義は<アカウントID>と<ユーザー名>の2つです。(タスク定義はGit上でスマートに変数化できず...)
{
"family": "devenv-test-developer-td",
"requiresCompatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"taskRoleArn": "arn:aws:iam::<アカウントID>:role/devenv-ecs-task-role",
"executionRoleArn": "arn:aws:iam::<アカウントID>:role/devenv-ecs-task-role",
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "app",
"image": "<アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/devenv-<ユーザー名>-ecr:latest",
"cpu": 0,
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/devenv-<ユーザー名>-log",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
git push(merge)
あとはmainにpushかmergeすればCI/CDが動作します。
Actionsはこんな感じ。
デプロイされました。
以上です。
作ってみればわかると思いますが、いろいろカスタマイズできるポイントがあるので、自由にカスタマイズして使ってみてください![]()









