ECS Fargate（Nginx + gRPC）構成のヘルスチェック設定

この記事はZOZOテクノロジーズ #2 Advent Calendar 2019 24日目の記事になります。
昨日は、@tsurumiiiさんの「[変更データ（CDC）を利用したデータ同期検討](https://qiita.com/tsurumiii/items/ed58f2e349573abeadec)」でした。

また、今年は全部で5つのAdvent Calendarが公開されています。

[ZOZOテクノロジーズ #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech)
[ZOZOテクノロジーズ #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech2)
[ZOZOテクノロジーズ #3 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech3)
[ZOZOテクノロジーズ #4 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech4)
[ZOZOテクノロジーズ #5 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech5)

## はじめに

　本記事は@gold-kouさんの[ECS+Fargateの環境でgRPCサーバのヘルスチェックをするために色々頑張った話](https://qiita.com/gold-kou/private/63befd8c6d50dcc5c2eb)で紹介されているgRPCヘルスチェックのインフラ面の設定の話になります。
　前提やこの構成に至った背景については上記の記事に丁寧にまとめられておりますのでご確認いただければと思います。


##構成

　タイトルの通りですが、NLBとECS Fargateの構成となっており、FargateのタスクではNginxとgRPCアプリケーションが稼働する構成となっております。

![NLB + ECS Fargate(Nginx + gRPC)構成のヘルスチェックについて (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/172ba3ea-1d9b-d7d5-f381-493a6934e573.jpeg)

　今回は内部通信の要件だったため、平文で通信しています。HTTP/2でTLS終端しようとすると、NLBはALPNに対応していないため問題となりうるのですが、今回は平文通信のためこの問題は回避できました。


##ヘルスチェック問題

　この構成で問題となるのがヘルスチェックです。
　今回は平文のgRPCを採用しているためターゲットグループのプロトコルはTCPを選択する必要があるのですが、この場合、ヘルスチェックで使用できるオプションは、TCP、HTTP、HTTPSの三つのみです。HTTP、HTTPSはgRPCメソッドを呼び出すことはできず、TCPはポート監視のみとなり、アプリケーションの死活監視を行うことができません。

## ECSのHEALTHCHECK機能

　上記の問題があることから、[ECSのHEALTHCHECK機能](https://aws.amazon.com/jp/about-aws/whats-new/2018/03/amazon-ecs-supports-container-health-checks-and-task-health-mana/)を利用して、ヘルスチェックを行うことにしました。
　Nginxコンテナ内にgrpc-health-probeをインストールし、ECSのHEALTHCHECK機能でNginxコンテナからgrpc-health-probeを実行するようにしました。
　CFnで設定する場合は以下のようになります。

```yml

  ECSTaskDefinition:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      Family: !Sub ${Prefix}-${Environment}
      RequiresCompatibilities:
        - 'FARGATE'
      Cpu: 1024
      Memory: 2048
      NetworkMode: 'awsvpc'
      ExecutionRoleArn: !GetAtt IAMRoleECSTaskExecution.Arn
      TaskRoleArn: !GetAtt IAMRoleAPI.Arn
      ContainerDefinitions:
        - Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositoryNginx}:latest
          Name: 'nginx'
          Cpu: 256
          MemoryReservation: 512
          PortMappings:
            - ContainerPort: 10080
              HostPort: 10080
              Protocol: 'tcp'
          # ここがHealthCheckの設定。指定したbashコマンドを実行できる。
          HealthCheck:
            Command: 
              - 'CMD-SHELL'
              - 'grpc-health-probe -addr=localhost:10080 -rpc-timeout 2s'
            Interval: 5
            Retries: 2
            StartPeriod: 10
            Timeout: 3
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub /ecs/${ECRRepositoryNginx}
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'ecs'
              awslogs-create-group: true
        - Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositorygRPC}:latest
          Name: 'grpc'
          Cpu: 256
          MemoryReservation: 512
          PortMappings:
            - ContainerPort: 8080
              HostPort: 8080
              Protocol: 'tcp'
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub /ecs/${ECRRepositorygRPC}
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'ecs'
              awslogs-create-group: true
          Essential: true
          Environment:
            - Name: 'AWS_DEFAULT_REGION'
              Value: !Ref AWS::Region
```

　この機能とNLBのTCPポート監視を組み合わせることで、異常のあるタスクがNLBから適切に切り離される挙動になることを期待しました。


## 実際の挙動

実際に検証してみたところ、想定通りに動きました。
ヘルスチェックの失敗からタスクのリタイアまでの流れとしては以下のようになります。

#### 1. gRPCアプリケーションが応答不可状態に陥る

![NLB + ECS Fargate(Nginx + gRPC)構成のヘルスチェックについて (4).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/a0903ff5-c427-d1ae-a170-155c5b45411e.jpeg)

#### 2. Nginxコンテナから実行されているヘルスチェックに失敗し、該当のタスクがUnhealthy状態になる

![NLB + ECS Fargate(Nginx + gRPC)構成のヘルスチェックについて (6).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/6ec1ad8c-7e0e-2771-edc2-247b47db5376.jpeg)

#### 3. NLBのTCPポートヘルスチェックに失敗し、Unhealthyのタスクが切り離される

![NLB + ECS Fargate(Nginx + gRPC)構成のヘルスチェックについて (9).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/922e2063-9bd7-6030-cdb4-276184b6dd03.jpeg)

#### 4. リタイアしたタスクに代わり、新規のタスクが起動する

![NLB + ECS Fargate(Nginx + gRPC)構成のヘルスチェックについて (10).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/f338cccd-28f3-936b-2de9-62d6807fcb7d.jpeg)

## さいごに

　少々苦戦しましたが、結果的にヘルスチェックを設定することができました。一つ機能が足りていなくても他の機能で補填でき、工夫すればなんとかなる点にはAWSにありがたみを感じています。
　ただ、AWSでgRPCを使う場合は、まだまだ機能が揃ってないと感じるケースが少なからずあるので、もう少し機能が充実してくるとありがたいなと思いました。

当記事の内容としては以上となります！

25日間に渡るアドベントカレンダーも明日で最後となります！！
弊社ではアドベントカレンダーが第5弾まであるので、5 * 25(日間)で全125記事となります。
是非とも他の記事にも目を通していただけますと幸いです！