Amazon ECSのタスク定義で改行やタブを含むファイルを出力する方法


数時間ほどハマってようやくたどり着いたのでメモ。

やりたいこと

Amazon ECSのタスクでコンテナ実行時に簡単なスクリプトファイルを作成して実行したかったのです。

hoge.py
print('hoge')
if False:
  print('no print')

みたいなPythonのファイルを作成して、python hoge.py するっていう。
インデントが効くか確認するためにif False: とかしてます。

docker run コマンドだと

お手元の端末でdocker コマンドが利用できる前提で。

cat コマンドでファイル作成

cat コマンドでヒアドキュメントを利用すると簡単にできます。
ヒアドキュメントについては下記が参考になります。

知ると便利なヒアドキュメント - Qiita
https://qiita.com/kite_999/items/e77fb521fc39454244e7

> docker run -it --rm --entrypoint="sh" \
  python:3.7 \
  -c "cat <<EOF > hoge.py
# hogeって出力
print('hoge')
if False:
  # インデントが効くか
  print('no print')
EOF
cat hoge.py;python hoge.py;"

# hogeって出力
print('hoge')
if False:
  # インデントが効くか
  print('no print')
hoge

なんとなくdocker run コマンド側のインデントに合わせたくなりますが、そうするとヒアドキュメントのルール「EOF が単独で現れると終了」から外れてしまうので、後続のコマンドが巻き込まれてしまいます。

> docker run -it --rm --entrypoint="sh" \
  python:3.7 \
  -c "cat <<EOF > hoge.py
  # hogeって出力
  print('hoge')
  if False:
    # インデントが効くか
    print('no print')
  EOF
  cat hoge.py;python hoge.py;"

# 出力なし

cat <<-EOF > hoge.py<<<<- に変更して行頭のタブを無視してみましたが、sh -c で実行しているのが原因なのかダメでした。またエディタを利用しているとタブ→スペース変換されたりするのでムダにハマりそうです。

Pythonの実装でもムダにインデントがあると怒られるので諦めます。

> docker run -it --rm --entrypoint="sh" \
  python:3.7 \
  -c "cat <<EOF > hoge.py
  # hogeって出力
  print('hoge')
  if False:
    # インデントが効くか
    print('no print')
EOF
  cat hoge.py;python hoge.py;"


  # hogeって出力
  print('hoge')
  if False:
    # インデントが効くか
    print('no print')
  File "hoge.py", line 2
    print('hoge')
    ^
IndentationError: unexpected indent

echo コマンドでファイル作成

echo コマンドでも複数行を改行付きで出力できるので、cat コマンドと同じことが実現できました。

Dockerfileで複数行を改行付きでechoする際にハマったので共有しておきます | ゲンゾウ用ポストイット
https://genzouw.com/entry/2019/04/17/080500

-c のあと、コンテナ内で実行するコマンドをダブルクォーテーション(" )で括り、echo コマンドで出力する文字列をエスケープ記号を付与したダブルクォーテーション(\" ) にして、Python実装ではシングルクォーテーション(' )を利用すると説明は長ったらしいですが、多分スッキリします。
こちらもインデントに気をつける必要がありました。

> docker run -it --rm --entrypoint="sh" \
  python:3.7 \
  -c "echo \"
# hogeって出力
print('hoge')
if False:
  # インデントが効くか
  print('no print')
\" > hoge.py;cat hoge.py;python hoge.py;"

# hogeって出力
print('hoge')
if False:
  # インデントが効くか
  print('no print')

hoge

Amazon ECSタスク定義だと

本題ですが、下記のように定義してやると実現できました。

マネジメントコンソールとかで

  • イメージ: python:3.7
  • エンドポイント: ["sh", "-c"]
  • コマンド: ["echo "# hogeって出力\nprint('hoge')\nif False:\n\t# インデントが効くか\n\tprint('no print')" > hoge.py;cat hoge.py;python hoge.py;"]

1行で定義しないとだめなので、改行を\n 、インデントのタブを\t に置き換える必要があります。(インデントは半角スペースでもOK)
試しに改行やタブをそのままでマネジメントコンソールのテキストボックスにコピペってみましたがダメでした(´・ω・`)

cat コマンドでヒアドキュメントを利用する方法も試してみましたが、どうにも思った結果が得られず断念しました。

AWS CloudFormationだと

AWS CloudFormationだとYAMLで定義できるのでおすすめです。
AWS::ECS::TaskDefinition の詳細については下記を参考ください。

AWS::ECS::TaskDefinition - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html

YAML中で改行を含ませる方法は下記を参考にさせてもらいました。

YAMLで値の中に改行を含ませる - 日々此妄想
https://hana-da.hatenadiary.org/entry/20081222/1229930738

テンプレート抜粋
Resources:
  (略)
  ECSTaskDefinitionSample:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: "ECSTaskDefinitionSample"
      Cpu: 256
      Memory: 512
      TaskRoleArn: !Ref ECSTaskRole
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name: test
          Image: python:3.7
          EntryPoint:
            - sh
            - -c
          Command:
            - |-
              echo "
              # hogeって出力
              print('hoge')
              if False:
                # インデントが効くか
                print('no print')
              " > hoge.py;
              cat hoge.py;
              python hoge.py;
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: /ecs/test/
              awslogs-region: !Sub "${AWS::Region}"
              awslogs-stream-prefix: hoge

作成したAmazon ESCのタスク定義からタスク実行すると下記のようにログ出力されて思ったとおりに実行されたことがわかります。
aws logs get-log-events コマンドでいい感じにログを取得する方法は下記が参考になります。

Amazon CloudWatch LogsのログをAWS CLIでいい感じに取得する - Qiita
https://qiita.com/kai_kou/items/60bbe314b74b9eaf7126

> aws logs get-log-events \
  --log-group-name <ロググループ名> \
  --log-stream-name <ストリーム名> \
  --query "events[].[message]" \
  --output text

# hogeって出力
print('hoge')
if False:
        # インデントが効くか
        print('no print')
hoge

まとめ

せっかくのコンテナサービスなんだからDockerイメージ作れって話ではあるのですが、ほんとにちょっとしたファイルを作成したいときに使えるかなと思います。

参考

知ると便利なヒアドキュメント - Qiita
https://qiita.com/kite_999/items/e77fb521fc39454244e7

Dockerfileで複数行を改行付きでechoする際にハマったので共有しておきます | ゲンゾウ用ポストイット
https://genzouw.com/entry/2019/04/17/080500

AWS::ECS::TaskDefinition - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html

YAMLで値の中に改行を含ませる - 日々此妄想
https://hana-da.hatenadiary.org/entry/20081222/1229930738

Amazon CloudWatch LogsのログをAWS CLIでいい感じに取得する - Qiita
https://qiita.com/kai_kou/items/60bbe314b74b9eaf7126