AWS Fargate Spotとは
- Fargate価格から最大70%割引
- 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の記事をそのまま参考にさせて頂き、express.jsでwebサーバーを実行してみます。
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.')
})
});
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"]
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ハンドラーに含めたりする必要がある。
参考