iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0
DevOps

AWS ECS + Gitlab + Laravel + Terraform 從入門到摔坑系列 第 17

Day 17 Terraform import container & deployment resource

  • 分享至 

  • xImage
  •  

今天要 import ECS 跟 CodePipeline 相關的 resource,分別把 ECS 的 resource 放到 container.tf 、CodePipeline 相關 resource 放到 codepipeline.tf 。(本日程式碼

ECS Cluster

我們會先 import service discovery namespace:

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:

import {
  to = aws_ecs_cluster.cluster
  id = "my-app"
}

generated configuration:

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:

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:

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:

import {
  to = aws_ecs_service.service
  id = "my-app/my-app"
}

id 是 ecs cluster 名稱/ecs service 名稱 ,一樣可以在 文件 查到。

generated configuration:

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"
  }
}

像上面的 clusteriam_roletask_definition 等等參數,只要可以改用 resource 的 reference 就改成 reference 比較好,不然這份 configuration 等於是 hardcode 的,會無法 reuse。

CodeBuild project 需要的 IAM Role 跟 Policy

IAM role 跟 IAM policy 的 import block:

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:

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:

import {
  to = aws_codebuild_project.build_img
  id = "my-app"
}

generated configuration:

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:

https://ithelp.ithome.com.tw/upload/images/20230927/20160671Y2YdjxvuRZ.png

直接把 concurrent_build_limit 刪掉。

CodePipeline 需要的 IAM role 及 policy

IAM role 及 policy 的 import block:

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:

import {
  to = aws_codepipeline.pipeline
  id = "my-app"
}

generated HCL:

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 完成後可以刪掉省得佔空間~


上一篇
Day 16 Terraform import compute resource
下一篇
Day 18 Terraform variable & 驗證 configuration
系列文
AWS ECS + Gitlab + Laravel + Terraform 從入門到摔坑30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言