├── .dockerignore ├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ ├── docker.yml │ └── release.yml ├── .gitignore ├── Dockerfile ├── LICENSE.rst ├── README.rst ├── ecs_deploy ├── __init__.py ├── cli.py ├── ecs.py ├── newrelic.py └── slack.py ├── requirements-test.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── test_cli.py ├── test_ecs.py ├── test_newrelic.py └── test_slack.py └── tox.ini /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | # Created by .ignore support plugin (hsz.mobi) 4 | ### Python template 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | 111 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: fabfuel 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Build 5 | 6 | on: 7 | push: 8 | branches: [ develop ] 9 | pull_request: 10 | branches: [ develop ] 11 | 12 | workflow_call: {} 13 | 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.12", "3.13"] 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v4 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install . 33 | pip install -r requirements-test.txt 34 | - name: Lint with flake8 35 | run: | 36 | # stop the build if there are Python syntax errors or undefined names 37 | flake8 ecs_deploy tests --count --select=E9,F63,F7,F82 --show-source --statistics 38 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 39 | flake8 ecs_deploy tests --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 40 | - name: Test with pytest --cov ecs_deploy 41 | run: | 42 | pytest 43 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker Hub 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'develop' 7 | - 'master' 8 | tags: 9 | - '*.*.*' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | 16 | steps: 17 | - 18 | name: Checkout 19 | uses: actions/checkout@v3 20 | - 21 | name: Login to Docker Hub 22 | uses: docker/login-action@v1 23 | with: 24 | username: fabfuel 25 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 26 | - 27 | name: Set up Docker Buildx 28 | uses: docker/setup-buildx-action@v1 29 | - 30 | name: Build and push 31 | uses: docker/build-push-action@v2 32 | with: 33 | context: . 34 | file: ./Dockerfile 35 | platforms: linux/amd64,linux/arm64 36 | push: true 37 | tags: fabfuel/ecs-deploy:${{ github.ref_name }} 38 | - 39 | name: "Build and push (tag: latest)" 40 | if: github.ref == 'refs/heads/develop' 41 | uses: docker/build-push-action@v2 42 | with: 43 | context: . 44 | file: ./Dockerfile 45 | platforms: linux/amd64,linux/arm64 46 | push: true 47 | tags: fabfuel/ecs-deploy:latest 48 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: [ '**' ] 6 | 7 | jobs: 8 | build: 9 | uses: ./.github/workflows/build.yml 10 | 11 | release: 12 | needs: 13 | - build 14 | runs-on: ubuntu-latest 15 | permissions: 16 | id-token: write 17 | contents: read 18 | strategy: 19 | fail-fast: false 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Set up Python 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: 3.12 27 | 28 | - name: Install tools 29 | run: | 30 | pip install build twine 31 | 32 | - name: Build project 33 | run: python -m build 34 | 35 | - name: Upload to PyPI 36 | env: 37 | TWINE_USERNAME: __token__ 38 | TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }} 39 | run: twine upload dist/* 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.pyc 3 | *.swp 4 | 5 | dist/ 6 | build/ 7 | env/ 8 | *.egg-info/ 9 | 10 | .tox/ 11 | .coverage 12 | .cache 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-alpine 2 | 3 | ADD . /usr/src/app 4 | WORKDIR /usr/src/app 5 | 6 | RUN ["python", "setup.py", "install"] 7 | 8 | CMD ["ecs"] 9 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | ecs-deploy 2 | 3 | Copyright (c) 2016-2019, Fabian Fuelling opensource@fabfuel.de. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided 6 | that the following conditions are met: 7 | 8 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following 9 | disclaimer. 10 | 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided with the distribution. 13 | 14 | Neither the name of Fabian Fuelling nor the names of his contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 23 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ECS Deploy 2 | ---------- 3 | 4 | .. image:: https://badge.fury.io/py/ecs-deploy.svg 5 | :target: https://badge.fury.io/py/ecs-deploy 6 | 7 | .. image:: https://github.com/fabfuel/ecs-deploy/actions/workflows/build.yml/badge.svg 8 | :target: https://github.com/fabfuel/ecs-deploy/actions/workflows/build.yml 9 | 10 | `ecs-deploy` simplifies deployments on Amazon ECS by providing a convenience CLI tool for complex actions, which are executed pretty often. 11 | 12 | Key Features 13 | ------------ 14 | - support for complex task definitions (e.g. multiple containers & task role) 15 | - easily redeploy the current task definition (including `docker pull` of eventually updated images) 16 | - deploy new versions/tags or all containers or just a single container in your task definition 17 | - scale up or down by adjusting the desired count of running tasks 18 | - add or adjust containers environment variables 19 | - run one-off tasks from the CLI 20 | - automatically monitor deployments in New Relic 21 | 22 | TL;DR 23 | ----- 24 | Deploy a new version of your service:: 25 | 26 | $ ecs deploy my-cluster my-service --tag 1.2.3 27 | 28 | Redeploy the current version of a service:: 29 | 30 | $ ecs deploy my-cluster my-service 31 | 32 | Scale up or down a service:: 33 | 34 | $ ecs scale my-cluster my-service 4 35 | 36 | Updating a cron job:: 37 | 38 | $ ecs cron my-cluster my-task my-rule 39 | 40 | Update a task definition (without running or deploying):: 41 | 42 | $ ecs update my-task 43 | 44 | 45 | Installation 46 | ------------ 47 | 48 | The project is available on PyPI. Simply run:: 49 | 50 | $ pip install ecs-deploy 51 | 52 | For [Homebrew](https://brew.sh/) users, you can also install [it](https://formulae.brew.sh/formula/ecs-deploy) via brew:: 53 | 54 | $ brew install ecs-deploy 55 | 56 | Run via Docker 57 | -------------- 58 | Instead of installing **ecs-deploy** locally, which requires a Python environment, you can run **ecs-deploy** via Docker. All versions starting from 1.7.1 are available on Docker Hub: https://cloud.docker.com/repository/docker/fabfuel/ecs-deploy 59 | 60 | Running **ecs-deploy** via Docker is easy as:: 61 | 62 | docker run fabfuel/ecs-deploy:1.10.2 63 | 64 | In this example, the stable version 1.10.2 is executed. Alternatively you can use Docker tags ``master`` or ``latest`` for the latest stable version or Docker tag ``develop`` for the newest development version of **ecs-deploy**. 65 | 66 | Please be aware, that when running **ecs-deploy** via Docker, the configuration - as described below - does not apply. You have to provide credentials and the AWS region via the command as attributes or environment variables:: 67 | 68 | docker run fabfuel/ecs-deploy:1.10.2 ecs deploy my-cluster my-service --region eu-central-1 --access-key-id ABC --secret-access-key ABC 69 | 70 | 71 | Configuration 72 | ------------- 73 | As **ecs-deploy** is based on boto3 (the official AWS Python library), there are several ways to configure and store the 74 | authentication credentials. Please read the boto3 documentation for more details 75 | (http://boto3.readthedocs.org/en/latest/guide/configuration.html#configuration). The simplest way is by running:: 76 | 77 | $ aws configure 78 | 79 | Alternatively you can pass the AWS credentials (via `--access-key-id` and `--secret-access-key`) or the AWS 80 | configuration profile (via `--profile`) as options when you run `ecs`. 81 | 82 | AWS IAM 83 | ------- 84 | 85 | If you are using **ecs-deploy** with a role or user account that does not have full AWS access, such as in a deploy script, you will 86 | need to use or create an IAM policy with the correct set of permissions in order for your deploys to succeed. One option is to use the 87 | pre-specified ``AmazonECS_FullAccess`` (https://docs.aws.amazon.com/AmazonECS/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonECS_FullAccess) policy. If you would prefer to create a role with a more minimal set of permissions, 88 | the following are required: 89 | 90 | * ``ecs:ListServices`` 91 | * ``ecs:UpdateService`` 92 | * ``ecs:ListTasks`` 93 | * ``ecs:RegisterTaskDefinition`` 94 | * ``ecs:DescribeServices`` 95 | * ``ecs:DescribeTasks`` 96 | * ``ecs:ListTaskDefinitions`` 97 | * ``ecs:DescribeTaskDefinition`` 98 | * ``ecs:DeregisterTaskDefinition`` 99 | 100 | If using custom IAM permissions, you will also need to set the ``iam:PassRole`` policy for each IAM role. See here https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_passrole.html for more information. 101 | 102 | Note that not every permission is required for every action you can take in **ecs-deploy**. You may be able to adjust permissions based on your specific needs. 103 | 104 | Actions 105 | ------- 106 | Currently the following actions are supported: 107 | 108 | deploy 109 | ====== 110 | Redeploy a service either without any modifications or with a new image, environment variable, docker label, and/or command definition. 111 | 112 | scale 113 | ===== 114 | Scale a service up or down and change the number of running tasks. 115 | 116 | run 117 | === 118 | Run a one-off task based on an existing task-definition and optionally override command, environment variables and/or docker labels. 119 | 120 | update 121 | ====== 122 | Update a task definition by creating a new revision to set a new image, 123 | environment variable, docker label, and/or command definition, etc. 124 | 125 | cron (scheduled task) 126 | ===================== 127 | Update a task definition and update a events rule (scheduled task) to use the 128 | new task definition. 129 | 130 | 131 | Usage 132 | ----- 133 | 134 | For detailed information about the available actions, arguments and options, run:: 135 | 136 | $ ecs deploy --help 137 | $ ecs scale --help 138 | $ ecs run --help 139 | 140 | Examples 141 | -------- 142 | All examples assume, that authentication has already been configured. 143 | 144 | Deployment 145 | ---------- 146 | 147 | Simple Redeploy 148 | =============== 149 | To redeploy a service without any modifications, but pulling the most recent image versions, run the following command. 150 | This will duplicate the current task definition and cause the service to redeploy all running tasks.:: 151 | 152 | $ ecs deploy my-cluster my-service 153 | 154 | 155 | Deploy a new tag 156 | ================ 157 | To change the tag for **all** images in **all** containers in the task definition, run the following command:: 158 | 159 | $ ecs deploy my-cluster my-service -t 1.2.3 160 | 161 | 162 | Deploy a new image 163 | ================== 164 | To change the image of a specific container, run the following command:: 165 | 166 | $ ecs deploy my-cluster my-service --image webserver nginx:1.11.8 167 | 168 | This will modify the **webserver** container only and change its image to "nginx:1.11.8". 169 | 170 | 171 | Deploy several new images 172 | ========================= 173 | The `-i` or `--image` option can also be passed several times:: 174 | 175 | $ ecs deploy my-cluster my-service -i webserver nginx:1.9 -i application my-app:1.2.3 176 | 177 | This will change the **webserver**'s container image to "nginx:1.9" and the **application**'s image to "my-app:1.2.3". 178 | 179 | Deploy a custom task definition 180 | =============================== 181 | To deploy any task definition (independent of which is currently used in the service), you can use the ``--task`` parameter. The value can be: 182 | 183 | A fully qualified task ARN:: 184 | 185 | $ ecs deploy my-cluster my-service --task arn:aws:ecs:eu-central-1:123456789012:task-definition/my-task:20 186 | 187 | A task family name with revision:: 188 | 189 | $ ecs deploy my-cluster my-service --task my-task:20 190 | 191 | Or just a task family name. It this case, the most recent revision is used:: 192 | 193 | $ ecs deploy my-cluster my-service --task my-task 194 | 195 | .. important:: 196 | ``ecs`` will still create a new task definition, which then is used in the service. 197 | This is done, to retain consistent behaviour and to ensure the ECS agent e.g. pulls all images. 198 | But the newly created task definition will be based on the given task, not the currently used task. 199 | 200 | 201 | Set an environment variable 202 | =========================== 203 | To add a new or adjust an existing environment variable of a specific container, run the following command:: 204 | 205 | $ ecs deploy my-cluster my-service -e webserver SOME_VARIABLE SOME_VALUE 206 | 207 | This will modify the **webserver** container definition and add or overwrite the environment variable `SOME_VARIABLE` with the value "SOME_VALUE". This way you can add new or adjust already existing environment variables. 208 | 209 | 210 | Adjust multiple environment variables 211 | ===================================== 212 | You can add or change multiple environment variables at once, by adding the `-e` (or `--env`) options several times:: 213 | 214 | $ ecs deploy my-cluster my-service -e webserver SOME_VARIABLE SOME_VALUE -e webserver OTHER_VARIABLE OTHER_VALUE -e app APP_VARIABLE APP_VALUE 215 | 216 | This will modify the definition **of two containers**. 217 | The **webserver**'s environment variable `SOME_VARIABLE` will be set to "SOME_VALUE" and the variable `OTHER_VARIABLE` to "OTHER_VALUE". 218 | The **app**'s environment variable `APP_VARIABLE` will be set to "APP_VALUE". 219 | 220 | 221 | Set environment variables exclusively, remove all other pre-existing environment variables 222 | ========================================================================================== 223 | To reset all existing environment variables of a task definition, use the flag ``--exclusive-env`` :: 224 | 225 | $ ecs deploy my-cluster my-service -e webserver SOME_VARIABLE SOME_VALUE --exclusive-env 226 | 227 | This will remove **all other** existing environment variables of **all containers** of the task definition, except for the variable `SOME_VARIABLE` with the value "SOME_VALUE" in the webserver container. 228 | 229 | 230 | Set a secret environment variable from the AWS Parameter Store 231 | ============================================================== 232 | 233 | .. important:: 234 | This option was introduced by AWS in ECS Agent v1.22.0. Make sure your ECS agent version is >= 1.22.0 or else your task will not deploy. 235 | 236 | To add a new or adjust an existing secret of a specific container, run the following command:: 237 | 238 | $ ecs deploy my-cluster my-service -s webserver SOME_SECRET KEY_OF_SECRET_IN_PARAMETER_STORE 239 | 240 | You can also specify the full arn of the parameter:: 241 | 242 | $ ecs deploy my-cluster my-service -s webserver SOME_SECRET arn:aws:ssm:::parameter/KEY_OF_SECRET_IN_PARAMETER_STORE 243 | 244 | This will modify the **webserver** container definition and add or overwrite the environment variable `SOME_SECRET` with the value of the `KEY_OF_SECRET_IN_PARAMETER_STORE` in the AWS Parameter Store of the AWS Systems Manager. 245 | 246 | 247 | Set secrets exclusively, remove all other pre-existing secret environment variables 248 | =================================================================================== 249 | To reset all existing secrets (secret environment variables) of a task definition, use the flag ``--exclusive-secrets`` :: 250 | 251 | $ ecs deploy my-cluster my-service -s webserver NEW_SECRET KEY_OF_SECRET_IN_PARAMETER_STORE --exclusive-secret 252 | 253 | This will remove **all other** existing secret environment variables of **all containers** of the task definition, except for the new secret variable `NEW_SECRET` with the value coming from the AWS Parameter Store with the name "KEY_OF_SECRET_IN_PARAMETER_STORE" in the webserver container. 254 | 255 | 256 | Set environment via .env files 257 | ============================== 258 | Instead of setting environment variables separately, you can pass a .env file per container to set the whole environment at once. You can either point to a local file or a file stored on S3, via:: 259 | 260 | $ ecs deploy my-cluster my-service --env-file my-app env/my-app.env 261 | 262 | $ ecs deploy my-cluster my-service --s3-env-file my-app arn:aws:s3:::my-ecs-environment/my-app.env 263 | 264 | Set secrets via .env files 265 | ============================== 266 | Instead of setting secrets separately, you can pass a .env file per container to set all secrets at once. 267 | 268 | This will expect an env file format, but any values will be set as the `valueFrom` parameter in the secrets config. 269 | This value can be either the path or the full ARN of a secret in the AWS Parameter Store. For example, with a secrets.env 270 | file like the following: 271 | 272 | ``` 273 | SOME_SECRET=arn:aws:ssm:::parameter/KEY_OF_SECRET_IN_PARAMETER_STORE 274 | ``` 275 | 276 | $ ecs deploy my-cluster my-service --secret-env-file webserver env/secrets.env 277 | 278 | This will modify the **webserver** container definition and add or overwrite the environment variable `SOME_SECRET` with the value of the `KEY_OF_SECRET_IN_PARAMETER_STORE` in the AWS Parameter Store of the AWS Systems Manager. 279 | 280 | 281 | Set a docker label 282 | =================== 283 | To add a new or adjust an existing docker labels of a specific container, run the following command:: 284 | 285 | $ ecs deploy my-cluster my-service -d webserver somelabel somevalue 286 | 287 | This will modify the **webserver** container definition and add or overwrite the docker label "somelabel" with the value "somevalue". This way you can add new or adjust already existing docker labels. 288 | 289 | 290 | Adjust multiple docker labels 291 | ============================= 292 | You can add or change multiple docker labels at once, by adding the `-d` (or `--docker-label`) options several times:: 293 | 294 | $ ecs deploy my-cluster my-service -d webserver somelabel somevalue -d webserver otherlabel othervalue -d app applabel appvalue 295 | 296 | This will modify the definition **of two containers**. 297 | The **webserver**'s docker label "somelabel" will be set to "somevalue" and the label "otherlabel" to "othervalue". 298 | The **app**'s docker label "applabel" will be set to "appvalue". 299 | 300 | 301 | Set docker labels exclusively, remove all other pre-existing docker labels 302 | ========================================================================== 303 | To reset all existing docker labels of a task definition, use the flag ``--exclusive-docker-labels`` :: 304 | 305 | $ ecs deploy my-cluster my-service -d webserver somelabel somevalue --exclusive-docker-labels 306 | 307 | This will remove **all other** existing docker labels of **all containers** of the task definition, except for the label "somelabel" with the value "somevalue" in the webserver container. 308 | 309 | 310 | Modify a command 311 | ================ 312 | To change the command of a specific container, run the following command:: 313 | 314 | $ ecs deploy my-cluster my-service --command webserver "nginx" 315 | 316 | This will modify the **webserver** container and change its command to "nginx". If you have 317 | a command that requires arguments as well, then you can simply specify it like this as you would normally do: 318 | 319 | $ ecs deploy my-cluster my-service --command webserver "ngnix -c /etc/ngnix/ngnix.conf" 320 | 321 | This works fine as long as any of the arguments do not contain any spaces. In case arguments to the 322 | command itself contain spaces, then you can use the JSON format: 323 | 324 | $ ecs deploy my-cluster my-service --command webserver '["sh", "-c", "while true; do echo Time files like an arrow $(date); sleep 1; done;"]' 325 | 326 | More about this can be looked up in documentation. 327 | https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definitions 328 | 329 | 330 | 331 | 332 | Set a task role 333 | =============== 334 | To change or set the role, the service's task should run as, use the following command:: 335 | 336 | $ ecs deploy my-cluster my-service -r arn:aws:iam::123456789012:role/MySpecialEcsTaskRole 337 | 338 | This will set the task role to "MySpecialEcsTaskRole". 339 | 340 | 341 | Set CPU and memory reservation 342 | ============================== 343 | - Set the `cpu` value for a task: :code:`--task-cpu 0`. 344 | - Set the `cpu` value for a task container: :code:`--cpu 0`. 345 | - Set the `memory` value (`hard limit`) for a task: :code:`--task-memory 256`. 346 | - Set the `memory` value (`hard limit`) for a task container: :code:`--memory 256`. 347 | - Set the `memoryreservation` value (`soft limit`) for a task definition: :code:`--memoryreservation 256`. 348 | 349 | Set privileged or essential flags 350 | ================================= 351 | - Set the `privileged` value for a task definition: :code:`--privileged True|False`. 352 | - Set the `essential` value for a task definition: :code:`--essential True|False`. 353 | 354 | Set logging configuration 355 | ========================= 356 | Set the `logConfiguration` values for a task definition:: 357 | 358 | --log awslogs awslogs-group 359 | --log awslogs awslogs-region 360 | --log awslogs awslogs-stream-prefix 361 | 362 | 363 | Set port mapping 364 | ================ 365 | - Set the `port mappings` values for a task definition: :code:`--port `. 366 | 367 | - Supports :code:`--exclusive-ports`. 368 | - The `protocol` is fixed to `tcp`. 369 | 370 | Set volumes & mount points 371 | ========================== 372 | - Set the `volumes` values for a task definition :code:`--volume /host/path`. 373 | 374 | - :code:`` can then be used with :code:`--mount`. 375 | - Set the `mount points` values for a task definition: :code:`--mount /container/path`. 376 | 377 | - Supports :code:`--exclusive-mounts`. 378 | 379 | - :code:`` is the one set by :code:`--volume`. 380 | - Set the `ulimits` values for a task definition: :code:`--ulimit memlock 67108864 67108864`. 381 | 382 | - Supports :code:`--exclusive-ulimits`. 383 | - Set the `systemControls` values for a task definition: :code:`--system-control net.core.somaxconn 511`. 384 | 385 | - Supports :code:`--exclusive-system-controls`. 386 | - Set the `healthCheck` values for a task definition: :code:`--health-check `. 387 | 388 | 389 | Set Health Checks 390 | ================= 391 | - Example :code:`--health-check webserver "curl -f http://localhost/alive/" 30 5 3 0` 392 | 393 | 394 | Placeholder Container 395 | ===================== 396 | - Add placeholder containers: :code:`--add-container `. 397 | - To comply with the minimum requirements for a task definition, a placeholder container is set like this: 398 | + The container name is :code:``. 399 | + The container image is :code:`PLACEHOLDER`. 400 | + The container soft limit is :code:`128`. 401 | - The idea is to set sensible values with the deployment. 402 | 403 | It is possible to add and define a new container with the same deployment:: 404 | 405 | --add-container redis --image redis redis:6 --port redis 6379 6379 406 | 407 | Remove containers 408 | ================= 409 | - Containers can be removed: :code:`--remove-container `. 410 | 411 | - Leaves the original containers, if all containers would be removed. 412 | 413 | 414 | All but the container flags can be used with `ecs deploy` and `ecs cron`. 415 | The container flags are used with `ecs deploy` only. 416 | 417 | 418 | Ignore capacity issues 419 | ====================== 420 | If your cluster is undersized or the service's deployment options are not optimally set, the cluster 421 | might be incapable to run blue-green-deployments. In this case, you might see errors like these: 422 | 423 | ERROR: (service my-service) was unable to place a task because no container instance met all of 424 | its requirements. The closest matching (container-instance 123456-1234-1234-1234-1234567890) is 425 | already using a port required by your task. For more information, see the Troubleshooting 426 | section of the Amazon ECS Developer Guide. 427 | 428 | There might also be warnings about insufficient memory or CPU. 429 | 430 | To ignore these warnings, you can run the deployment with the flag ``--ignore-warnings``:: 431 | 432 | $ ecs deploy my-cluster my-service --ignore-warnings 433 | 434 | In that case, the warning is printed, but the script continues and waits for a successful 435 | deployment until it times out. 436 | 437 | Deployment timeout 438 | ================== 439 | The deploy and scale actions allow defining a timeout (in seconds) via the ``--timeout`` parameter. 440 | This instructs ecs-deploy to wait for ECS to finish the deployment for the given number of seconds. 441 | 442 | To run a deployment without waiting for the successful or failed result at all, set ``--timeout`` to the value of ``-1``. 443 | 444 | 445 | Multi-Account Setup 446 | =================== 447 | If you manage different environments of your system in multiple differnt AWS accounts, you can now easily assume a 448 | deployment role in the target account in which your ECS cluster is running. You only need to provide ``--account`` 449 | with the AWS account id and ``--assume-role`` with the name of the role you want to assume in the target account. 450 | ecs-deploy automatically assumes this role and deploys inside your target account: 451 | 452 | Example:: 453 | 454 | $ ecs deploy my-cluster my-service --account 1234567890 --assume-role ecsDeployRole 455 | 456 | 457 | 458 | 459 | Scaling 460 | ------- 461 | 462 | Scale a service 463 | =============== 464 | To change the number of running tasks and scale a service up and down, run this command:: 465 | 466 | $ ecs scale my-cluster my-service 4 467 | 468 | 469 | Running a Task 470 | -------------- 471 | 472 | Run a one-off task 473 | ================== 474 | To run a one-off task, based on an existing task-definition, run this command:: 475 | 476 | $ ecs run my-cluster my-task 477 | 478 | You can define just the task family (e.g. ``my-task``) or you can run a specific revision of the task-definition (e.g. 479 | ``my-task:123``). And optionally you can add or adjust environment variables like this:: 480 | 481 | $ ecs run my-cluster my-task:123 -e my-container MY_VARIABLE "my value" 482 | 483 | 484 | Run a task with a custom command 485 | ================================ 486 | 487 | You can override the command definition via option ``-c`` or ``--command`` followed by the container name and the 488 | command in a natural syntax, e.g. no conversion to comma-separation required:: 489 | 490 | $ ecs run my-cluster my-task -c my-container "python some-script.py param1 param2" 491 | 492 | The JSON syntax explained above regarding modifying a command is also applicable here. 493 | 494 | 495 | Run a task in a Fargate Cluster 496 | =============================== 497 | 498 | If you want to run a one-off task in a Fargate cluster, additional configuration is required, to instruct AWS e.g. which 499 | subnets or security groups to use. The required parameters for this are: 500 | 501 | - launchtype 502 | - securitygroup 503 | - subnet 504 | - public-ip 505 | 506 | Example:: 507 | 508 | $ ecs run my-fargate-cluster my-task --launchtype=FARGATE --securitygroup sg-01234567890123456 --subnet subnet-01234567890123456 --public-ip 509 | 510 | You can pass multiple ``subnet`` as well as multiple ``securitygroup`` values. the ``public-ip`` flag determines, if the task receives a public IP address or not. 511 | Please see ``ecs run --help`` for more details. 512 | 513 | 514 | Monitoring 515 | ---------- 516 | With ECS deploy you can track your deployments automatically. Currently only New Relic is supported: 517 | 518 | New Relic 519 | ========= 520 | To record a deployment in New Relic, you can provide the the API Key (**Attention**: this is a specific REST API Key, not the license key) and the application id in two ways: 521 | 522 | Via cli options:: 523 | 524 | $ ecs deploy my-cluster my-service --newrelic-apikey ABCDEFGHIJKLMN --newrelic-appid 1234567890 525 | 526 | Or implicitly via environment variables ``NEW_RELIC_API_KEY`` and ``NEW_RELIC_APP_ID`` :: 527 | 528 | $ export NEW_RELIC_API_KEY=ABCDEFGHIJKLMN 529 | $ export NEW_RELIC_APP_ID=1234567890 530 | $ ecs deploy my-cluster my-service 531 | 532 | Optionally you can provide additional information for the deployment: 533 | 534 | - ``--comment "New feature X"`` - comment to the deployment 535 | - ``--user john.doe`` - the name of the user who deployed with 536 | - ``--newrelic-revision 1.0.0`` - explicitly set the revision to use for the deployment 537 | 538 | Note: If neither ``--tag`` nor ``--newrelic-revision`` are provided, the deployment will not be recorded. 539 | 540 | 541 | Troubleshooting 542 | --------------- 543 | If the service configuration in ECS is not optimally set, you might be seeing 544 | timeout or other errors during the deployment. 545 | 546 | Timeout 547 | ======= 548 | The timeout error means, that AWS ECS takes longer for the full deployment cycle then ecs-deploy is told to wait. The deployment itself still might finish successfully, if there are no other problems with the deployed containers. 549 | 550 | You can increase the time (in seconds) to wait for finishing the deployment via the ``--timeout`` parameter. This time includes the full cycle of stopping all old containers and (re)starting all new containers. Different stacks require different timeout values, the default is 300 seconds. 551 | 552 | The overall deployment time depends on different things: 553 | 554 | - the type of the application. For example node.js containers tend to take a long time to get stopped. But nginx containers tend to stop immediately, etc. 555 | - are old and new containers able to run in parallel (e.g. using dynamic ports)? 556 | - the deployment options and strategy (Maximum percent > 100)? 557 | - the desired count of running tasks, compared to 558 | - the number of ECS instances in the cluster 559 | 560 | 561 | Alternative Implementation 562 | -------------------------- 563 | There are some other libraries/tools available on GitHub, which also handle the deployment of containers in AWS ECS. If you prefer another language over Python, have a look at these projects: 564 | 565 | Shell 566 | ecs-deploy - https://github.com/silinternational/ecs-deploy 567 | 568 | Ruby 569 | broadside - https://github.com/lumoslabs/broadside 570 | -------------------------------------------------------------------------------- /ecs_deploy/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = '1.15.2' 2 | -------------------------------------------------------------------------------- /ecs_deploy/cli.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, absolute_import 2 | 3 | from os import getenv 4 | from time import sleep 5 | 6 | import click 7 | import json 8 | import getpass 9 | from datetime import datetime, timedelta 10 | from botocore.exceptions import ClientError 11 | from ecs_deploy import VERSION 12 | from ecs_deploy.ecs import DeployAction, ScaleAction, RunAction, EcsClient, DiffAction, \ 13 | TaskPlacementError, EcsError, UpdateAction, LAUNCH_TYPE_EC2, LAUNCH_TYPE_FARGATE 14 | from ecs_deploy.newrelic import Deployment, NewRelicException 15 | from ecs_deploy.slack import SlackNotification 16 | 17 | 18 | @click.group() 19 | @click.version_option(version=VERSION, prog_name='ecs-deploy') 20 | def ecs(): # pragma: no cover 21 | pass 22 | 23 | 24 | def get_client(access_key_id, secret_access_key, region, profile, assume_account, assume_role): 25 | return EcsClient(access_key_id, secret_access_key, region, profile, assume_account=assume_account, assume_role=assume_role) 26 | 27 | 28 | @click.command() 29 | @click.argument('cluster') 30 | @click.argument('service') 31 | @click.option('-t', '--tag', help='Changes the tag for ALL container images') 32 | @click.option('-i', '--image', type=(str, str), multiple=True, help='Overwrites the image for a container: ') 33 | @click.option('-c', '--command', type=(str, str), multiple=True, help='Overwrites the command in a container: ') 34 | @click.option('-h', '--health-check', type=(str, str, int, int, int, int), multiple=True, help='Overwrites the healthcheck in a container: ') 35 | @click.option('--cpu', type=(str, int), multiple=True, help='Overwrites the cpu value for a container: ') 36 | @click.option('--memory', type=(str, int), multiple=True, help='Overwrites the memory value for a container: ') 37 | @click.option('--memoryreservation', type=(str, int), multiple=True, help='Overwrites the memory reservation value for a container: ') 38 | @click.option('--task-cpu', type=int, help='Overwrites the cpu value for a task: ') 39 | @click.option('--task-memory', type=int, help='Overwrites the memory value for a task: ') 40 | @click.option('--privileged', type=(str, bool), multiple=True, help='Overwrites the privileged value for a container: ') 41 | @click.option('--essential', type=(str, bool), multiple=True, help='Overwrites the essential value for a container: ') 42 | @click.option('-e', '--env', type=(str, str, str), multiple=True, help='Adds or changes an environment variable: ') 43 | @click.option('--env-file', type=(str, str), default=((None, None),), multiple=True, required=False, help='Load environment variables from .env-file: ') 44 | @click.option('--s3-env-file', type=(str, str), multiple=True, required=False, help='Location of .env-file in S3 in ARN format (eg arn:aws:s3:::/bucket_name/object_name): ') 45 | @click.option('-s', '--secret', type=(str, str, str), multiple=True, help='Adds or changes a secret environment variable from the AWS Parameter Store (Not available for Fargate): ') 46 | @click.option('--secrets-env-file', type=(str, str), default=((None, None),), multiple=True, required=False, help='Load secrets from .env-file: ') 47 | @click.option('-d', '--docker-label', type=(str, str, str), multiple=True, help='Adds or changes a docker label: ') 48 | @click.option('-u', '--ulimit', type=(str, str, int, int), multiple=True, help='Adds or changes a ulimit variable in the container description (Not available for Fargate): ') 49 | @click.option('--system-control', type=(str, str, str), multiple=True, help='Adds or changes a system control variable in the container description (Not available for Fargate): ') 50 | @click.option('-p', '--port', type=(str, int, int), multiple=True, help='Adds or changes a port mappings in the container description (Not available for Fargate): ') 51 | @click.option('-m', '--mount', type=(str, str, str), multiple=True, help='Adds or changes a mount points in the container description (Not available for Fargate): ') 52 | @click.option('-l', '--log', type=(str, str, str, str), multiple=True, help='Adds or changes a log configuration in the container description (Not available for Fargate):