|
| 1 | +--- |
| 2 | +title: "ECS Partial Task Definitions" |
| 3 | +sidebar_label: "Partial Task Definitions" |
| 4 | +sidebar_position: 20 |
| 5 | +--- |
| 6 | + |
| 7 | +import Intro from '@site/src/components/Intro'; |
| 8 | +import Steps from '@site/src/components/Steps'; |
| 9 | + |
| 10 | +<Intro> |
| 11 | + This document describes what partial task definitions are and how we can use them to set up ECS services using Terraform and GitHub Actions. |
| 12 | +</Intro> |
| 13 | + |
| 14 | +## The Problem |
| 15 | + |
| 16 | +Managing ECS Services is challenging. Ideally, we want our services to be managed by Terraform so everything is living |
| 17 | +in code. However, we also want to update the task definition via GitOps as through the GitHub release lifecycle. This is |
| 18 | +challenging because Terraform can create the task definition, but if updated by the application repository, the task |
| 19 | +definition will be out of sync with the Terraform state. |
| 20 | + |
| 21 | +Managing it entirely through Terraform means we cannot easily update the newly built image by the application repository |
| 22 | +unless we directly commit to the infrastructure repository, which is not ideal. |
| 23 | + |
| 24 | +Managing it entirely through the application repository means we cannot codify the infrastructure and have to hardcode |
| 25 | +ARNs, secrets, and other infrastructure-specific configurations. |
| 26 | + |
| 27 | +## Introduction |
| 28 | + |
| 29 | +ECS Partial task definitions is the idea of breaking the task definition into smaller parts. This allows for easier |
| 30 | +management of the task definition and makes it easier to update the task definition. |
| 31 | + |
| 32 | +We do this by setting up Terraform to manage a portion of the task definition, and the application repository to manage |
| 33 | +another portion. |
| 34 | + |
| 35 | +The Terraform (infrastructure) portion is created first. It will create an ECS Service in ECS, and then upload the task |
| 36 | +definition JSON to S3 as `task-template.json`.The application repository will have a `task-definition.json` git |
| 37 | +controlled, during the development lifecycle, the application repository will download the task definition from S3, |
| 38 | +merge the task definitions, then update the ECS Service with the new task definition. Finally, GitHub actions will |
| 39 | +update the S3 bucket with the deployed task definition under `task-definition.json`. If Terraform is planned again, it |
| 40 | +will use the new task definition as the base for the next deployment, thus not resetting the image or application |
| 41 | +configuration. |
| 42 | + |
| 43 | +<img src="/assets/ecs-partial-task-definitions.png" /><br/> |
| 44 | + |
| 45 | +### Pros |
| 46 | + |
| 47 | +The **benefit** to using this approach is that we can manage the task definition portion in Terraform with the |
| 48 | +infrastructure, meaning secrets, volumes, and other ARNs can be managed in Terraform. If a filesystem ID updates we can |
| 49 | +re-apply Terraform to update the task definition with the new filesystem ID. The application repository can manage the |
| 50 | +container definitions, environment variables, and other application-specific configurations. This allows developers who |
| 51 | +are closer to the application to quickly update the environment variables or other configuration. |
| 52 | + |
| 53 | +### Cons |
| 54 | + |
| 55 | +The drawback to this approach is that it is more complex than managing the task definition entirely in Terraform or the |
| 56 | +application repository. It requires more setup and more moving parts. It can be confusing for a developer who is not |
| 57 | +familiar with the setup to understand how the task definition is being managed and deployed. |
| 58 | + |
| 59 | +This also means that when something goes wrong, it becomes harder to troubleshoot as there are more moving parts. |
| 60 | + |
| 61 | +### Getting Setup |
| 62 | + |
| 63 | +#### Pre-requisites |
| 64 | + |
| 65 | +- Application Repository - [Cloud Posse Example ECS Application](https://github.com/cloudposse-examples/app-on-ecs) |
| 66 | +- Infrastructure Repository |
| 67 | +- ECS Cluster - [Cloud Posse Docs](https://docs.cloudposse.com/components/library/aws/ecs/) - |
| 68 | + [Component](https://github.com/cloudposse/Terraform-aws-components/tree/main/modules/ecs). |
| 69 | +- `ecs-service` - [Cloud Posse Docs](https://docs.cloudposse.com/components/library/aws/ecs-service/) - |
| 70 | + [Component](https://github.com/cloudposse/Terraform-aws-components/tree/main/modules/ecs-service). |
| 71 | + - **Must** use the Cloud Posse Component. |
| 72 | + - [`v1.416.0`](https://github.com/cloudposse/Terraform-aws-components/releases/tag/1.416.0) or later. |
| 73 | +- S3 Bucket - [Cloud Posse Docs](https://docs.cloudposse.com/components/library/aws/s3-bucket/) - |
| 74 | + [Component](https://github.com/cloudposse/Terraform-aws-components/tree/main/modules/s3-bucket). |
| 75 | + |
| 76 | +#### Steps |
| 77 | + |
| 78 | +<Steps> |
| 79 | + |
| 80 | +1. Set up the S3 Bucket that will store the task definition. |
| 81 | + |
| 82 | + <br/>This bucket should be in the same account as the ECS Cluster. |
| 83 | + |
| 84 | + <br/> |
| 85 | + <details> |
| 86 | + <summary>S3 Bucket Default Definition</summary> |
| 87 | + |
| 88 | + ```yaml |
| 89 | + components: |
| 90 | + terraform: |
| 91 | + s3-bucket/defaults: |
| 92 | + metadata: |
| 93 | + type: abstract |
| 94 | + vars: |
| 95 | + enabled: true |
| 96 | + account_map_tenant_name: core |
| 97 | + # Suggested configuration for all buckets |
| 98 | + user_enabled: false |
| 99 | + acl: "private" |
| 100 | + grants: null |
| 101 | + force_destroy: false |
| 102 | + versioning_enabled: false |
| 103 | + allow_encrypted_uploads_only: true |
| 104 | + block_public_acls: true |
| 105 | + block_public_policy: true |
| 106 | + ignore_public_acls: true |
| 107 | + restrict_public_buckets: true |
| 108 | + allow_ssl_requests_only: true |
| 109 | + lifecycle_configuration_rules: |
| 110 | + - id: default |
| 111 | + enabled: true |
| 112 | + abort_incomplete_multipart_upload_days: 90 |
| 113 | + filter_and: |
| 114 | + prefix: "" |
| 115 | + tags: {} |
| 116 | + # Move to Glacier after 2 years |
| 117 | + transition: |
| 118 | + - storage_class: GLACIER |
| 119 | + days: 730 |
| 120 | + # Never expire |
| 121 | + expiration: {} |
| 122 | + # Versioning isnt enabled, but these default values are still required |
| 123 | + noncurrent_version_transition: |
| 124 | + - storage_class: GLACIER |
| 125 | + days: 90 |
| 126 | + noncurrent_version_expiration: {} |
| 127 | + ``` |
| 128 | +
|
| 129 | + </details> |
| 130 | +
|
| 131 | + ```yaml |
| 132 | + import: |
| 133 | + - catalog/s3-bucket/defaults |
| 134 | + |
| 135 | + components: |
| 136 | + terraform: |
| 137 | + s3-bucket/ecs-tasks-mirror: #NOTE this is the component instance name. |
| 138 | + metadata: |
| 139 | + component: s3-bucket |
| 140 | + inherits: |
| 141 | + - s3-bucket/defaults |
| 142 | + vars: |
| 143 | + enabled: true |
| 144 | + name: ecs-tasks-mirror |
| 145 | + ``` |
| 146 | +
|
| 147 | +2. Create an ECS Service in Terraform |
| 148 | +
|
| 149 | + <br/>Set up the ECS Service in Terraform using the |
| 150 | + [`ecs-service` component](https://github.com/cloudposse/Terraform-aws-components/tree/main/modules/ecs-service). This |
| 151 | + will create the ECS Service and upload the task definition to the S3 bucket. |
| 152 | + |
| 153 | + <br/>To enable Partial Task Definitions, set the variable `s3_mirror_name` to be the component instance name of the |
| 154 | + bucket to mirror to. For example `s3-bucket/ecs-tasks-mirror` |
| 155 | + |
| 156 | + ```yaml |
| 157 | + components: |
| 158 | + terraform: |
| 159 | + ecs-services/defaults: |
| 160 | + metadata: |
| 161 | + component: ecs-service |
| 162 | + type: abstract |
| 163 | + vars: |
| 164 | + enabled: true |
| 165 | + ecs_cluster_name: "ecs/cluster" |
| 166 | + s3_mirror_name: s3-bucket/ecs-tasks-mirror |
| 167 | + ``` |
| 168 | + |
| 169 | +3. Set up an Application repository with GitHub workflows. |
| 170 | + |
| 171 | + An example application repository can be found [here](https://github.com/cloudposse-examples/app-on-ecs). |
| 172 | + |
| 173 | + <br/> Two things need to be pulled from this repository: |
| 174 | + |
| 175 | + - The `task-definition.json` file under `deploy/task-definition.json` |
| 176 | + - The GitHub Workflows. |
| 177 | + |
| 178 | + An important note about the GitHub Workflows, in the example repository they all live under `.github/workflows`. This |
| 179 | + is done so development of workflows can be fast, however we recommend moving the shared workflows to a separate |
| 180 | + repository and calling them from the application repository. The application repository should only contain the |
| 181 | + workflows `main-branch.yaml`, `release.yaml` and `feature-branch.yml`. |
| 182 | + |
| 183 | + <br/>To enable Partial Task Definitions in the workflows, the call to |
| 184 | + [`cloudposse/github-action-run-ecspresso` (link)](https://github.com/cloudposse-examples/app-on-ecs/blob/main/.github/workflows/workflow-cd-ecspresso.yml#L133-L147) |
| 185 | + should have the input `mirror_to_s3_bucket` set to the S3 bucket name. the variable `use_partial_taskdefinition` |
| 186 | + should be set to `'true'` |
| 187 | + |
| 188 | + <details> |
| 189 | + <summary> Example GitHub Action Step </summary> |
| 190 | + |
| 191 | + ```yaml |
| 192 | + - name: Deploy |
| 193 | + uses: cloudposse/github-action-deploy-ecspresso@0.6.0 |
| 194 | + continue-on-error: true |
| 195 | + if: ${{ steps.db_migrate.outcome != 'failure' }} |
| 196 | + id: deploy |
| 197 | + with: |
| 198 | + image: ${{ steps.image.outputs.out }} |
| 199 | + image-tag: ${{ inputs.tag }} |
| 200 | + region: ${{ steps.environment.outputs.region }} |
| 201 | + operation: deploy |
| 202 | + debug: false |
| 203 | + cluster: ${{ steps.environment.outputs.cluster }} |
| 204 | + application: ${{ steps.environment.outputs.name }} |
| 205 | + taskdef-path: ${{ inputs.path }} |
| 206 | + mirror_to_s3_bucket: ${{ steps.environment.outputs.s3-bucket }} |
| 207 | + use_partial_taskdefinition: "true" |
| 208 | + timeout: 10m |
| 209 | + ``` |
| 210 | + |
| 211 | + </details> |
| 212 | + |
| 213 | +</Steps> |
| 214 | + |
| 215 | +## Operation |
| 216 | + |
| 217 | +Changes through Terraform will not immediately be reflected in the ECS Service. This is because the task template has |
| 218 | +been updated, but whatever was in the `task-definition.json` file in the S3 bucket will be used for deployment. |
| 219 | + |
| 220 | +To update the ECS Service after updating the Terraform for it, you must deploy through GitHub Actions. This will then |
| 221 | +download the new template and create a new updated `task-defintion.json` to store in s3. |
0 commit comments