AWS Fargate Spot を使う前のメモ

## AWS Fargate Spotとは

- Fargate価格から**最大７０%割引**
- EC2スポットインスタンスとコンセプト自体は同じ
- AWSクラウドの空きキャパシティを活用してタスクを実行できる
- AWSがキャパシティを必要とした時、Fargate Spotで稼働するタスクは**2分前の通知**とともに中断される
- 常時稼働すべきタスクとFargate Spotで稼働するタスクを併用して構成することができる

Fargate Spotを利用する場合、ECSクラスターを作成する際に**キャパシティープロバイダー**を指定する必要があります。

## キャパシティープロバイダーとは

- キャパシティープロバイダー戦略で使用されて、タスクが実行されるインフラストラクチャを決定する
- 各クラスターには1つ以上のキャパシティープロバイダーがある

Fargateの場合、`FARGATE`と`FARGATE_SPOT`という2つのキャパシティープロバイダーがあります。これらは予約されているため、作成する必要がありません。

## キャパシティープロバイダー戦略とは

- クラスターの複数のキャパシティープロバイダー間にタスクを分散する方法を決定する
- 各クラスターにはデフォルトのキャパシティープロバイダー戦略が関連づけられている

## ECSタスクの正常なシャットダウン（Graceful Shutdown）

Fargate SpotではAWSの都合でタスクが中断されるため、タスク終了の2分前の通知を受けた後、必要に応じてタスクは正常なシャットダウン（**Graceful Shutdown**）を開始する必要があります。

タスクが停止するとき、**SIGTERM**シグナルが各コンテナのエントリプロセス（**PID:1**）に送信されます。

タイムアウトが経過すると、今度は**SIGKILL**シグナルがプロセスに送信されます。

デフォルトではタイムアウト値は**30秒**なので、SIGTERMシグナルが送られてからSIGKILLシグナルが来るまでの30秒の間にプロセスを正常にする必要があります（タイムアウト値を変更することは可能）。

### Graceful Shutdownを模擬的に試してみる

コンテナでは`Dockerfile`の`ENTRYPOINT`および`CMD`ディレクティブで指定されたプロセスがここでいうエントリプロセスであり、コンテナ内の他の全てのプロセスの親となります。

ここでは[Express (Node.js) の Graceful shutdown](https://qiita.com/megmogmog1965/items/86da1dcb42cb5c6a4d14)の記事をそのまま参考にさせて頂き、express.jsでwebサーバーを実行してみます。

```js:index.js
const express = require('express');

const SLEEP_MSEC = 30 * 1000;
const app = express();

app.get('/sleep', (req, res) => {
  setTimeout(() => res.send('OK'), SLEEP_MSEC);
});

const server = app.listen(3000, () => console.log('Example app listening on port 3000!'));

process.on('SIGTERM', () => {
  server.close(() => {
    console.log('Process terminated.')
  })
});
```

```Dockerfile:Dockerfile
FROM node:14
ENV LANG C.UTF-8

WORKDIR /usr/src/
ADD package.json package.json
ADD package-lock.json package-lock.json
RUN npm install

ADD index.js index.js

EXPOSE 3000

CMD ["node", "index.js"]
```

```docker-compose.yml
version: "3"

services:
  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    working_dir: /usr/src
    ports:
      - "3000:3000"
```

`docker-compose up`で立ち上げた後、`docker-compose exec api /bin/bash`で入った後、`ps aux`で確認すると、

```
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.9 598316 37272 ?        Ssl  09:17   0:00 node index.js
root        15  0.1  0.0  19952  3540 pts/0    Ss   09:20   0:00 /bin/bash
root        25  0.0  0.0  38384  3064 pts/0    R+   09:21   0:00 ps aux
```

`node index.js`がエントリプロセスになっていることが確認できます。

ここで`curl http://localhost:3000/sleep`すると10秒後にレスポンスが返ってきますが、その10秒の間にwebサーバーに対して`kill 1`でSIGTERMシグナルを送ってみます。
その直後、同じく`curl http://localhost:3000/sleep`すると、下記のようにサーバーからレスポンスが返ってこなくなりました。
```
> curl http://localhost:3000/sleep
curl: (52) Empty reply from server
```
一方で。10秒経つと最初のリクエストに対するレスポンスが返ってきました。
```
> curl http://localhost:3000/sleep
ok
```
つまり、SIGTERMシグナル受信前の処理中のリクエストは正常に処理を完了し、SIGTERMシグナル受信後に来た新規のリクエストは受け付ないようになっています。

→ ECSタスクがSIGTERMシグナルを送ってきた時も同じ挙動を想定して良い？

### ロードバランサーを使っている場合、ターゲットグループからタスクを自前で解除する必要がある

> FARGATE_SPOT として実行されるタスクでは、ロードバランサーのターゲットグループから登録解除されてからタスクが STOPPED 状態に移行するという保証はありません。
> https://aws.amazon.com/jp/blogs/news/graceful-shutdowns-with-ecs/

FARGATE_SPOTの場合、自前で登録解除するロジックをSIGTERMハンドラーに含めたりする必要がある。

## 参考

https://aws.amazon.com/jp/blogs/news/aws-fargate-spot-now-generally-available/

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/bestpracticesguide/ec2-and-fargate-spot.html

https://d1.awsstatic.com/webinars/jp/pdf/services/202109_AWS_Black_Belt_Containers303-ECS-Spot-Fargate.pdf

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/cluster-capacity-providers.html

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/fargate-capacity-providers.html


https://aws.amazon.com/jp/blogs/news/graceful-shutdowns-with-ecs/

https://qiita.com/megmogmog1965/items/86da1dcb42cb5c6a4d14