5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Terraform】AWS上で完結する開発環境を簡単に量産できるシステムの作り方(ECS / GitHub Actions / SageMaker AI)

Last updated at Posted at 2025-12-09

AWS管理者の方で、開発者に対してAWS上にユーザー、開発環境、デプロイ先のフルセットをすぐ提供できたらな...と思うことはないでしょうか。

この思いを解決すべく、このフルセットをTerraformで書いてみました。同じ思いの方がいましたら、ぜひ使ってみてください🙏

構成

下記のような環境を簡単にいくつでもTerraformを使って払い出すことができます。青矢印がTerraform、オレンジ矢印が開発者の利用の流れです。

image.png

先に構築しておくもの

  • 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 を設定 → シングルユーザー向けの設定 (クイックセットアップ)

image.png

IAM

作るのは開発者用のIAMグループと、ECSロール、SageMaker AIロールです。この環境では権限はゆるくしてるので共通で作りました。自分の環境だけに対して権限を与えるということもできるので、必要なら適宜行ってください。SageMaker AIはプロファイルとIAMユーザーを紐付けて、特定のIAMユーザーしか使えないようにしたりすることもできます。

ネットワーク

VPC、サブネット、ALBを作ります。今回はすべてパブリックサブネットに配置しています。セキュアにするならプライベートサブネットを作成して、そこにECSを配置、NATを作る必要があります。

またこのシステムではドメインをとっていないので、https化もしていません。必要ならドメインをとって、https化を行なってください。

ALBはパスが存在しないアクセスを404にしています。

network.tf
# 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しておきます。長いので一部だけ記載します。

outputs.tf
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をつけてください。

outputs.tf
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サービスを作ります。

リスナールールは開発者がパスを決められるように変数にしています。

main.tf
# 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.tf
# リモートステート
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リスナーの優先度
terraform.tfvars
# 固定
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でのみデプロイとしています。必要ならプルリクなどでも動作を加えてください。また、ユーザー名がユーザー自身で入力が必要なので、変数にしています。

workflows.yaml
...
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に切り替わります。

task-definition.json
...
"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です。

terraform.tfvars
# 固定
sagemaker_domain_id = "XXX"

# ユーザーごとに変更
developer_name    = "test-developer"
path_name         = "test-system"
listener_priority = 200

apply後、サービスにアクセスしてみましょう。

スクリーンショット 2025-12-05 17.08.49.png

Git リポジトリのコピー

Use this template → Create a new repository

スクリーンショット 2025-12-05 17.18.23.png

リポジトリはもちろんこのリポジトリを使っても良いですが、アプリ部分を自由に編集いただいて、新たなテンプレートとして提供しても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

image.png

SageMaker AI / Code Editorの利用

ここからは実際に開発者が行う操作です。

SageMaker AI → 該当ドメイン → 自分のプロファイル → Code Editor

スクリーンショット 2025-12-05 17.11.25.png

Code Editor → Create Code Editor space →(スペースができたら) Open

スクリーンショット 2025-12-05 17.15.04.png

これで見慣れたVSCodeの画面が出てくるので、コピーしたGitリポジトリをcloneしましょう。

clone後の画面です。

image.png

Code Editorのスペースについて

拡張とインストール

Code Editorのスペースは停止すると、拡張機能やインストールしたものがすべて削除されます。開発環境を維持したいのであれば、スペースは停止しない設定にしておくか、起動のイメージがカスタマイズできるので、そこで拡張機能やインストールを仕込んでください。

/home/sagemaker-user/ 配下のデータはスペースが停止しても残り続けます

ローカルでのビルドとデプロイ

一応、Dockerと拡張機能を組み合わせれば可能です。いろいろ調べてやってみてください。

git push(workflows.yamlとtask-definition.jsonの編集)

コード内にデプロイのための変数があるので、編集します。

workflows.yaml

DEVELOPER_NAMEをtfvarsのときに使ったユーザー名へ編集します。

workflows.yaml
...
jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      # ユーザー名を変数にしておく
      DEVELOPER_NAME: test-developer
...

task-definition.json

タスク定義は<アカウントID><ユーザー名>の2つです。(タスク定義はGit上でスマートに変数化できず...)

task-definition.json
{
  "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)

あとはmainpushmergeすればCI/CDが動作します。

Actionsはこんな感じ。

image.png

デプロイされました。

スクリーンショット 2025-12-05 17.47.44.png

以上です。

作ってみればわかると思いますが、いろいろカスタマイズできるポイントがあるので、自由にカスタマイズして使ってみてください:wrench:

5
2
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
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?