今天要 import ECS 跟 CodePipeline 相關的 resource,分別把 ECS 的 resource 放到 container.tf
、CodePipeline 相關 resource 放到 codepipeline.tf
。(本日程式碼)
ECS Cluster
我們會先 import service discovery namespace:
1 2 3 4
| import { to = aws_service_discovery_http_namespace.app id = "ns-oqfsu4obz4lghp3e" }
|
這讓 ECS service 可以使用 AWS Cloud Map 來管理 service 的 DNS entry、讓 VPC 內的其他人可以找到這些 service。雖然本系列文不會用到它,但是 web console 有幫我們建立並且關聯到 cluster,所以還是 import 一下。
ECS cluster import block:
1 2 3 4
| import { to = aws_ecs_cluster.cluster id = "my-app" }
|
generated configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| resource "aws_ecs_cluster" "cluster" { name = "my-app" tags = {} tags_all = {} configuration { execute_command_configuration { kms_key_id = null logging = "DEFAULT" } } service_connect_defaults { namespace = "arn:aws:servicediscovery:ap-northeast-1:xxxxxxxx:namespace/ns-oqfsu4obz4lghp3e" } setting { name = "containerInsights" value = "disabled" } }
|
Task Definition
import block:
1 2 3 4
| import { to = aws_ecs_task_definition.td id = "arn:aws:ecs:ap-northeast-1:xxxxxxx:task-definition/my-app:5" }
|
注意 id
最後的 revision,我們要 import 最新的 revision。
generated configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| resource "aws_ecs_task_definition" "td" { container_definitions = "[{\"cpu\":0,\"environment\":[],\"environmentFiles\":[],\"essential\":true,\"image\":\"xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-app@sha256:61230330c3d91df6266740cb27174953c7bc1e5a3e3cc9dd709e546b276c5947\",\"mountPoints\":[],\"name\":\"my-app\",\"portMappings\":[{\"appProtocol\":\"http\",\"containerPort\":80,\"hostPort\":0,\"name\":\"my-app-80-tcp\",\"protocol\":\"tcp\"}],\"ulimits\":[],\"volumesFrom\":[]}]" cpu = "1024" execution_role_arn = null family = "my-app" ipc_mode = null memory = "512" network_mode = "bridge" pid_mode = null requires_compatibilities = ["EC2"] skip_destroy = null tags = {} tags_all = {} task_role_arn = null runtime_platform { cpu_architecture = "X86_64" operating_system_family = "LINUX" } }
|
可以看到 container 的 definition 就搞成一串很難看的字串…..如果很受不了,可以用 HCL syntax 改寫,然後用 jsonencode()
包起來,會比較容易閱讀。
ECS service
import block:
1 2 3 4
| import { to = aws_ecs_service.service id = "my-app/my-app" }
|
id 是 ecs cluster 名稱/ecs service 名稱
,一樣可以在 文件 查到。
generated configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| resource "aws_ecs_service" "service" { cluster = "arn:aws:ecs:ap-northeast-1:xxxxxxx:cluster/my-app" deployment_maximum_percent = 200 deployment_minimum_healthy_percent = 100 desired_count = 0 enable_ecs_managed_tags = true enable_execute_command = false force_new_deployment = null health_check_grace_period_seconds = 0 iam_role = "/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS" launch_type = "EC2" name = "my-app" platform_version = null propagate_tags = "NONE" scheduling_strategy = "REPLICA" tags = {} tags_all = {} task_definition = "my-app:5" triggers = {} wait_for_steady_state = null deployment_circuit_breaker { enable = true rollback = true } deployment_controller { type = "ECS" } load_balancer { container_name = "my-app" container_port = 80 elb_name = null target_group_arn = "arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxx:targetgroup/tg-my-app/8b85ca755abbba68" } ordered_placement_strategy { field = "attribute:ecs.availability-zone" type = "spread" } ordered_placement_strategy { field = "instanceId" type = "spread" } }
|
像上面的 cluster
、iam_role
、task_definition
等等參數,只要可以改用 resource 的 reference 就改成 reference 比較好,不然這份 configuration 等於是 hardcode 的,會無法 reuse。
CodeBuild project 需要的 IAM Role 跟 Policy
IAM role 跟 IAM policy 的 import block:
1 2 3 4 5 6 7 8 9
| import { to = aws_iam_role.code_build_project id = "codebuild-my-app-service-role" }
import { to = aws_iam_policy.code_build_project id = "arn:aws:iam::384137370331:policy/service-role/CodeBuildBasePolicy-my-app-ap-northeast-1" }
|
IAM policy generated config:
1 2 3 4 5 6 7 8 9
| resource "aws_iam_policy" "code_build_project" { description = "Policy used in trust relationship with CodeBuild" name = "CodeBuildBasePolicy-my-app-ap-northeast-1" name_prefix = null path = "/service-role/" policy = "{\"Statement\":[{\"Action\":[\"logs:CreateLogGroup\",\"logs:CreateLogStream\",\"logs:PutLogEvents\"],\"Effect\":\"Allow\",\"Resource\":[\"arn:aws:logs:ap-northeast-1:xxxxxxx:log-group:/aws/codebuild/my-app\",\"arn:aws:logs:ap-northeast-1:xxxxxxx:log-group:/aws/codebuild/my-app:*\"]},{\"Action\":[\"s3:PutObject\",\"s3:GetObject\",\"s3:GetObjectVersion\",\"s3:GetBucketAcl\",\"s3:GetBucketLocation\"],\"Effect\":\"Allow\",\"Resource\":[\"arn:aws:s3:::codepipeline-ap-northeast-1-*\"]},{\"Action\":[\"codebuild:CreateReportGroup\",\"codebuild:CreateReport\",\"codebuild:UpdateReport\",\"codebuild:BatchPutTestCases\",\"codebuild:BatchPutCodeCoverages\"],\"Effect\":\"Allow\",\"Resource\":[\"arn:aws:codebuild:ap-northeast-1:xxxxxxxxx:report-group/my-app-*\"]}],\"Version\":\"2012-10-17\"}" tags = {} tags_all = {} }
|
可以看到上面 policy 超級多,這也是筆者一開始會選擇 import 的原因之一。如果對於各種 AWS 服務跟 IAM policy 還不熟悉,光是權限問題就可以把人逼瘋(O)。直接開最高權限當然可以解決使用問題,然後製造安全上的問題(O),權限設定的原則是「所需要的最少權限」而不是為了方便大家通通有最高權限。
如果不透過 web console 直接建立再 import,還有兩個方法可以寫出 policy:第一是找找看 AWS 文件有沒有現成範例,第二則是要執行的操作跑下去(像這邊是 codebuild 的 project),從錯誤訊息看缺什麼就補什麼,補到操作可以順利完成(所謂的把人逼瘋…)。
CodeBuild project
import block:
1 2 3 4
| import { to = aws_codebuild_project.build_img id = "my-app" }
|
generated configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| resource "aws_codebuild_project" "build_img" { badge_enabled = false build_timeout = 60 concurrent_build_limit = 0 description = null encryption_key = "arn:aws:kms:ap-northeast-1:xxxxxx:alias/aws/s3" name = "my-app" project_visibility = "PRIVATE" queued_timeout = 480 resource_access_role = null service_role = "arn:aws:iam::xxxxxx:role/service-role/codebuild-my-app-service-role" source_version = null tags = {} tags_all = {} artifacts { artifact_identifier = null bucket_owner_access = null encryption_disabled = false location = null name = "my-app" namespace_type = null override_artifact_name = false packaging = "NONE" path = null type = "CODEPIPELINE" } cache { location = null modes = [] type = "NO_CACHE" } environment { certificate = null compute_type = "BUILD_GENERAL1_SMALL" image = "aws/codebuild/standard:7.0" image_pull_credentials_type = "CODEBUILD" privileged_mode = false type = "LINUX_CONTAINER" } logs_config { cloudwatch_logs { group_name = null status = "DISABLED" stream_name = null } s3_logs { bucket_owner_access = null encryption_disabled = false location = null status = "DISABLED" } } source { buildspec = "version: 0.2\nphases:\n build:\n commands:\n - ContainerName=\"my-app\"\n - ImageURI=$(cat imageDetail.json | jq -r '.ImageURI')\n - printf '[{\"name\":\"CONTAINER_NAME\",\"imageUri\":\"IMAGE_URI\"}]' > imagedefinitions.json\n - sed -i -e \"s|CONTAINER_NAME|$ContainerName|g\" imagedefinitions.json\n - sed -i -e \"s|IMAGE_URI|$ImageURI|g\" imagedefinitions.json\n \nartifacts:\n files:\n - imagedefinitions.json" git_clone_depth = 0 insecure_ssl = false location = null report_build_status = false type = "CODEPIPELINE" } }
|
遇到 error:

直接把 concurrent_build_limit
刪掉。
CodePipeline 需要的 IAM role 及 policy
IAM role 及 policy 的 import block:
1 2 3 4 5 6 7 8 9
| import { to = aws_iam_role.codepipeline id = "AWSCodePipelineServiceRole-ap-northeast-1-my-app" }
import { to = aws_iam_policy.codepipeline id = "arn:aws:iam::xxxxxxx:policy/service-role/AWSCodePipelineServiceRole-ap-northeast-1-my-app" }
|
generated configuration 因為 policy 很多很多所以很長,就不貼了~這邊的 import 都不太有遇到什麼 error。
CodePipeline
import block:
1 2 3 4
| import { to = aws_codepipeline.pipeline id = "my-app" }
|
generated HCL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| resource "aws_codepipeline" "pipeline" { name = "my-app" role_arn = "arn:aws:iam::xxxxxx:role/service-role/AWSCodePipelineServiceRole-ap-northeast-1-my-app" tags = {} tags_all = {} artifact_store { location = "codepipeline-ap-northeast-1-239650800043" region = null type = "S3" } stage { name = "Source" action { category = "Source" configuration = { RepositoryName = "my-app" } input_artifacts = [] name = "Source" namespace = "SourceVariables" output_artifacts = ["SourceArtifact"] owner = "AWS" provider = "ECR" region = "ap-northeast-1" role_arn = null run_order = 1 version = "1" } } stage { name = "Build" action { category = "Build" configuration = { ProjectName = "my-app" } input_artifacts = ["SourceArtifact"] name = "Build" namespace = "BuildVariables" output_artifacts = ["BuildArtifact"] owner = "AWS" provider = "CodeBuild" region = "ap-northeast-1" role_arn = null run_order = 1 version = "1" } } stage { name = "Deploy" action { category = "Deploy" configuration = { ClusterName = "my-app" ServiceName = "my-app" } input_artifacts = ["BuildArtifact"] name = "Deploy" namespace = "DeployVariables" output_artifacts = [] owner = "AWS" provider = "ECS" region = "ap-northeast-1" role_arn = null run_order = 1 version = "1" } } }
|
好!終於!經過一番無聊的 import,我們終於(應該)把所有 resource import 進 terraform 了!import block 在 import 完成後可以刪掉省得佔空間~