☕️

ecspresso advent calendar 2020 day 10 - diff

2020/12/10に公開

Amazon ECS のデプロイツールである ecspresso の利用法をまとめていく ecspresso Advent calendar 10日目です。

現在の定義ファイルと ECS 上の状態の差分を表示する diff コマンド

現在のサービス/タスク定義ファイルと実際に ECS 上にデプロイされている状態の差分を出力するのが ecspresso diff コマンドです。

ecspresso 以外の方法で ECS の状態を変更している可能性がある場合は、デプロイによって意図せぬ変更が発生しないかを確認したいでしょう。定義ファイルのリファクタリングを行った場合に、結果として壊していないかを確認するためにも diff コマンドは利用できます。

diff コマンドの実行例

例として、サービス定義の platformVersion とタスク定義の image を変更し、タスク定義に環境変数を追加した場合の結果を示します。

結果は標準出力に、unified 形式で出力されます。

$ ecspresso --config config.yaml diff
--- arn:aws:ecs:ap-northeast-1:123456789012:service/ecspresso-demo/nginx-service
+++ ecs-service-def.json
 {
   "deploymentConfiguration": {
     "deploymentCircuitBreaker": {
       "enable": false,
       "rollback": false
     },
     "maximumPercent": 200,
     "minimumHealthyPercent": 100
   },
   "healthCheckGracePeriodSeconds": 0,
   "networkConfiguration": {
     "awsvpcConfiguration": {
       "assignPublicIp": "ENABLED",
       "securityGroups": [
         "sg-043eab2d606362f03"
       ],
       "subnets": [
         "subnet-0089ed3e1bdff1fc9",
         "subnet-0d750adbd139f411d"
       ]
     }
   },
   "placementConstraints": [],
   "placementStrategy": [],
-  "platformVersion": "LATEST"
+  "platformVersion": "1.4.0"
 }
 
--- arn:aws:ecs:ap-northeast-1:123456789012:task-definition/first-run-task-definition:2
+++ ecs-task-def.json
 {
   "containerDefinitions": [
     {
       "command": [],
       "cpu": 256,
       "entryPoint": [],
-      "environment": [],
+      "environment": [
+        {
+          "name": "FOO",
+          "value": "BAR"
+        }
+      ],
       "essential": true,
-      "image": "nginx:latest",
+      "image": "nginx:stable",
       "links": [],
       "logConfiguration": {
         "logDriver": "awslogs",
         "options": {
           "awslogs-group": "/ecs/first-run-task-definition",
           "awslogs-region": "ap-northeast-1",
           "awslogs-stream-prefix": "ecs"
         }
       },
       "memoryReservation": 512,
       "mountPoints": [],
       "name": "nginx",
       "portMappings": [
         {
           "containerPort": 80,
           "hostPort": 80,
           "protocol": "tcp"
         }
       ],
       "secrets": [],
       "volumesFrom": []
     }
   ],
   "cpu": "256",
   "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
   "family": "first-run-task-definition",
   "memory": "512",
   "networkMode": "awsvpc",
   "placementConstraints": [],
   "requiresCompatibilities": [
     "FARGATE"
   ],
   "volumes": []
 }

差分がなければ、ecspresso diff はなにも出力せずに終了します。

diff コマンドの実装裏話

diff コマンドを実装するのは意外と大変でした。サービス/タスク定義ファイルをレンダリングしたものと、ECS の API で取得した結果を単純に比べればよいかというと、次の理由からそうではないためです。

  • ECS API で取得した結果には、登録はできないが ECS 上には存在する値が含まれている
    • 例えば作成日時の createdAt など
    • 定義ファイルに入れる意味がない値なので、差分として表示する意味がないため除外する必要があります
  • タスク定義の environment, secrets などの値は JSON 上での表現は配列だが、実際には順不同である
    • ["A", "B"] という値を登録後、取り出してみると ["B", "A"] となって返ってくることがあります
    • 実用上は順序には意味がない(保持されている必要がない)値ですが、比較する時には無意味な差分として検出されてしまうため、ソートしてから比較しています
  • 順序に意味がある配列もある
    • 例えばコンテナ定義の command 要素などです
    • ["curl", "-sL", "http://example.com/"] をソートして ["-sL", "curl", "http://example.com/"] にすると意味が変わってしまいます。これはソートしてはいけません
  • 登録した値がそのまま返ってこない要素がある
    • タスク定義の cpu, memory などです
    • cpu の定義では "1 vCPU" のような表現が利用できますが、API から取得すると "1024" となって返ってきます
    • 設定としては等価ですが差分として検出する意味はないため、表現を揃えてから比較する必要があります

興味があるかたはソースコードの diff.go を覗いてみてください。reflect を利用して型に依存しないソートを実装した結果、これはあとから読めないかも知れない…と若干後悔しているので、そのうち実装は変わるかもしれません。

diff コマンドのオプション

$ ecspresso diff --help
usage: ecspresso diff

display diff for task definition compared with latest one on ECS

Flags:
  --help           Show context-sensitive help (also try --help-long and --help-man).
  --config=CONFIG  config file
  --debug          enable debug log
  --color          enalble colored output

--color

出力結果を色つきにします。端末で実行されている場合にはデフォルトで有効、端末以外で実行されている場合にはデフォルトで無効 (--no-color) になります。


11日目は、サービス/タスク定義が依存する各種リソースが利用可能かどうかを検証する方法を説明します。

https://zenn.dev/fujiwara/articles/ecspresso-20201211

Discussion