はじめに
AWSのECS(Elastic Container Service)やAuroraを触っていると、必ず出てくるvCPU・メモリ・ACU。
毎回「これなんだっけ、どういう役割でどう使われるんだっけ」となり、調べて「あーなるほどね」となって、また忘れる。これを繰り返していました。
原因は分かっていて、この3つを「同じ種類のもの」として覚えようとしていたからです。
設定画面では数字を入れる欄が並んでいるので同じように見えますが、実際は測っているものがそれぞれ違います。
そこを整理したくて、この記事を書きました。
結論:測っている「ものさし」が違う
| 単位 | 何のものさしか | ひとことで |
|---|---|---|
| vCPU | 仮想環境に割り当てられたCPUリソースの量 | どれだけCPU処理を使えるか |
| メモリ | 作業領域の大きさ | どれだけデータを置いて処理できるか |
| ACU | Aurora Serverless v2の計算リソースの容量 | CPU・メモリなどをまとめた容量 |
vCPU — 仮想環境に割り当てるCPUリソースの量
vCPUの「v」はVirtual(仮想) を意味します。
vCPUは、仮想環境に割り当てられたCPUの処理量を表す単位です。
ECS / Fargateでは、1 vCPU = 1024 CPU unitsとして指定します。
- 256 = 0.25 vCPU
- 512 = 0.5 vCPU
- 1024 = 1 vCPU
なぜ 1024 なのか
ECS は内部で Docker を使っており、Docker の CPU 割り当てオプション(--cpu-shares)のデフォルト値が 1024 だったことに由来します。
さらに元をたどると Linux の cgroups の既定値です。
そもそもCPU・コア・vCPUの違いは?
まず、物理的なPCを考えます。
物理PC
└─ CPU
├─ コア1 ← 計算処理をする部分
├─ コア2 ← 計算処理をする部分
├─ コア3 ← 計算処理をする部分
└─ コア4 ← 計算処理をする部分
CPUは計算処理をするハードウェアで、その中に実際に計算処理を行う「コア」が複数あります。
一方、AWSなどのクラウドでは、物理的なCPUやコアを利用者が直接扱うのではなく、仮想化された環境にCPUリソースを割り当てます。
物理サーバー
└─ 物理CPU・コア
↓ 仮想化
仮想環境
↓
vCPU
つまり、
- CPU → 物理的な計算処理をするハードウェア
- コア → CPUの中にある、実際に計算処理をする部分
- vCPU → クラウドなどの仮想環境に割り当てられるCPUリソースの単位
という関係です。
AWSで設定するのはどれか
AWSを使う側が基本的に指定するのはvCPUです。
コアの数や物理CPUの個数を直接指定するわけではありません。
「4 vCPUのタスク」を選ぶ
↓
AWSが必要な物理リソースを割り当てる
↓
利用者からは4 vCPUのCPUリソースとして見える
物理的なCPUやコアの構成はAWS側が管理していて、利用者からはvCPUという単位でリソースを扱います。
ECSのタスク定義で「1024」と書くのも、このvCPUの割り当てに関する設定です。
メモリ — 作業領域の大きさ
メモリは、プログラムが処理中のデータなどを一時的に置いておく作業領域です。
保存容量(ディスク)とは異なり、処理中のデータを扱うために使われます。
単位にはMiB(メビバイト)やGiB(ギビバイト)が使われます。
- 512 MiB = 0.5 GiB
- 2048 MiB = 2 GiB
GiBとGBは少し違う
GiBは2進法、GBは10進法の単位です。
- 1 GiB = 1024 MiB
- 1 GB = 1000 MB
そのため、厳密には2048 MiB = 2 GiB ≒ 2.147 GBです。
AWSの画面では「GB」と表示される場合もありますが、サービスによって単位の扱いが異なるため、容量を厳密に計算するときは確認が必要です。
vCPUとメモリが不足するとどうなる?
割り当てたvCPUでは処理量が足りない場合、CPU処理に時間がかかります。
一方、メモリが不足すると、処理に必要な作業領域を確保できず、OOMによってプロセスやコンテナが終了することがあります。
タスクのCPU・メモリをコンテナに分ける
ECSのタスク定義では、CPU・メモリをタスクレベルとコンテナレベルの2か所で指定できます。
1つのタスクの中で、アプリ本体と、サイドカー(=同じタスクの中で横に並べて動かす補助のコンテナ)を一緒に動かすことがあります。このとき、タスクレベルで確保した量を、コンテナどうしで分け合います。
タスク定義(cpu: 1024 / memory: 2048)
├─ app … アプリ本体
└─ log-router … ログ転送のサイドカー
この例では、タスクレベルで確保した1 vCPU / 2 GiBを、2つのコンテナで分け合います。
コンテナレベルで指定できる3つの値
| 設定 | 意味 | 超えたときの挙動 |
|---|---|---|
cpu |
CPUの取り分(CPU units) | ほかのコンテナが使っていなければ、超えて使える |
memory |
メモリのハード上限 | 超えるとそのコンテナが強制終了される |
memoryReservation |
メモリの最低確保量(ソフト上限) | 空きがあれば、超えて使える |
{
"cpu": "1024",
"memory": "2048",
"containerDefinitions": [
{ "name": "app", "cpu": 768, "memoryReservation": 1536 },
{ "name": "log-router", "cpu": 128, "memory": 256, "essential": false }
]
}
コンテナに割り当てた合計(CPU 896 / メモリ 1792)を、タスクの値(1024 / 2048)より少し小さくしています。理由は下の注意書きを見てください。
CPUとメモリで考え方が違う
cpuは「取り合いになったときの比率」です。
上の例でlog-routerが何もしていなければ、appは768を超えてCPUを使えます。
一方、memory(ハード上限)は絶対的な制限です。超えた瞬間に、そのコンテナが強制終了されます。
1つのコンテナが落ちたとき、タスクはどうなるか
コンテナが落ちたときにタスク全体が止まるかどうかは、essential(=そのコンテナがタスクに必須かどうか) の設定で決まります。
essential |
そのコンテナが止まったとき |
|---|---|
true(省略したときもこちら) |
タスク内のほかのコンテナもすべて止まる |
false |
ほかのコンテナには影響しない |
上の例では、log-routerをessential: falseにしています。log-routerがメモリの上限を超えて落ちても、appは動き続けます。逆に、appはessentialを省略しているのでtrue扱いです。appが落ちると、タスク全体が止まります。
障害の影響をコンテナ単位に閉じ込めたいときは、止まっても困らないコンテナだけessential: falseにするのがポイントです。
コンテナに確保するメモリの合計は、タスクのmemoryより小さくする
公式ドキュメントでは、タスク内の全コンテナに確保するメモリの合計は、タスクレベルのmemoryより小さくする必要があるとされています。CPUも同じで、全コンテナのcpuの合計はタスクレベルのcpuより小さくします。
また、コンテナレベルの値を省略した場合は、タスクレベルの値を各コンテナで共有します。
終了コード137はメモリ不足のサイン
コンテナが突然停止し、終了コードが137だった場合、多くはメモリのハード上限超過(OOMKill)です。
128 + SIGKILL(9) で137になります。
アプリ側の設定も合わせる
Node.jsやJavaなどのランタイムは、自分でヒープ(=プログラムが使うメモリ領域)の上限を決めます。バージョンや設定によっては、コンテナに割り当てた量とずれた上限になり、OOMで落ちたり、逆にメモリを使い切れなかったりします。
コンテナに割り当てた量より少し小さい値を、アプリ側でも明示しておくと安全です。
# app に割り当てた 1536 MiB に対して、少し余裕を持たせる
NODE_OPTIONS=--max-old-space-size=1280
ACU — Aurora Serverless v2の容量を表す単位
ACUは「Aurora Capacity Unit」の略で、Aurora Serverless v2で使われる容量の単位です。
Auroraには、あらかじめ決められたスペックのDBインスタンスを選んで使う方法と、負荷に応じて容量が変化するAurora Serverless v2があります。
Aurora Serverless v2では、DBインスタンスのスペックを固定的に選ぶのではなく、ACUという単位で容量を指定します。
1 ACUは、おおよそ2 GiBのメモリを基準として、CPUやネットワークなどのリソースをまとめた容量です。
つまり、ACUはCPUだけ、メモリだけを表す単位ではありません。
ACUはvCPU・メモリと何が違う?
① CPU・メモリを個別に設定しない
ECSでは、CPUとメモリをそれぞれ設定します。
一方、Aurora Serverless v2では、ACUを指定すると、CPU・メモリなどのリソースがACUに応じてまとめて変化します。
② 固定値ではなく「範囲」を指定する
ここがACUの大きな特徴です。
ECS / Fargateでは、タスクに割り当てるCPU・メモリの量をあらかじめ設定します。
「1 vCPU / 2 GiBのタスク」と設定
↓
そのタスクは
CPU:1 vCPU
メモリ:2 GiB
負荷が増えても、1タスクに割り当てたCPU・メモリの量が自動で増えるわけではありません。
一方、Aurora Serverless v2では、ACUの範囲を設定します。
最小:0.5 ACU
最大:16 ACU
負荷が増えると、その範囲内でAuroraが使用する計算リソースを自動的に増やします。
負荷が下がれば減らします。
そのため、実際のキャパシティは負荷に応じて自動的にスケールします。
vCPU・メモリ・ACUを比較
| 項目 | vCPU | メモリ | ACU |
|---|---|---|---|
| 何を表す? | 仮想環境に割り当てるCPUリソースの量 | 作業領域の大きさ | Aurora Serverless v2の計算リソースの容量 |
| 何を指定する? | CPUの量 | メモリの量 | 最小ACU・最大ACU |
| CPUとの関係 | CPUそのものの処理量を表す | CPUが処理するときに使う作業領域 | CPU・メモリなどをまとめて扱う |
| リソースの変化 | 1タスクに設定した量が割り当てられる | 1タスクに設定した量が割り当てられる | 負荷に応じて自動的に増減 |
| 主に使われる場所 | ECS / Fargateなど | ECS / Fargateなど | Aurora Serverless v2 |
まとめ
- vCPU = 仮想環境に割り当てるCPUリソースの量。ECS / Fargateでは1024 CPU units = 1 vCPU
- 1 vCPU = 1コアとは限らない。vCPUと物理コアの対応関係は、CPUの種類やクラウドサービスによって異なる
- メモリ = プログラムが処理中のデータなどを置いておく作業領域
- 割り当てたvCPUでは処理量が足りない場合、処理に時間がかかり、メモリが不足するとOOMによってプロセスやコンテナが終了する可能性がある
- ECSでは、タスクで確保したCPU・メモリをコンテナごとに分けられる。CPUは取り合いのときの比率、メモリの
memoryは超えると落ちる上限 - コンテナが落ちたときにタスクごと止まるかは
essentialで決まる - ACU = Aurora Serverless v2で使われる容量の単位
- ACUはCPU・メモリなどをまとめて扱い、最小値と最大値の範囲で指定する