Kubernetes には Taint と Toleration という仕組みがあり、これらを使うことで Pod が配置される Node を制御できる。この Node にはこの Pod しか配置を許可しない、といった指定が可能になる。この記事では Taint と Toleration の基本的な使い方を見ていく。
この記事の内容は Kubernetes の1.33で動作確認した。
Taint
Kubernetes では Node に Pod が配置されるわけだが、 Node の属性のひとつに Taint というものがある。
これを使うと、特定の条件を満たした Pod 以外はその Node に配置されないようになる。
Taint はkey,value,effectの組み合わせで設定する。
keyとvalueは Taint のラベルのようなもので、 Taint を識別するために使う。valueはオプションなので指定しなくてもよい。
effectは 3 種類あり、具体的にどのようなルールを課すか指定する。
NoExecute- 新規 Pod のスケジューリングをブロックし、既存の Pod も、条件を満たさない場合は退去させる
- 後述する Toleration で
TolerationSecondsが設定されている場合、即座には退去されず、指定された秒数だけ退去が猶予される
- 後述する Toleration で
- 新規 Pod のスケジューリングをブロックし、既存の Pod も、条件を満たさない場合は退去させる
NoSchedule- 新規 Pod のスケジューリングをブロックする。既存の Pod には影響しない。
PreferNoSchedule- 可能な限り新規 Pod のスケジューリングを避けるが、他に選択肢がない場合は配置を許可する
例を示していくための環境として、 2 つの Node があるクラスタを Amazon EKS に用意する。
terraform { cloud { organization = "sample-org" workspaces { name = "eks-workspace" } } required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = "ap-northeast-1" } # IAM ロールやサブネットは既に別途作成済みであり、 Terraform では定義していない data "aws_iam_role" "eks_cluster_role" { name = "my-eks-cluster-role" } data "aws_iam_role" "eks_node_role" { name = "my-eks-node-role" } locals { subnet_ids = [ "subnet-111", "subnet-222" ] } resource "aws_eks_cluster" "my_cluster" { name = "my-cluster" role_arn = data.aws_iam_role.eks_cluster_role.arn vpc_config { subnet_ids = local.subnet_ids } } resource "aws_eks_node_group" "node_x" { cluster_name = aws_eks_cluster.my_cluster.name node_group_name = "node-x" node_role_arn = data.aws_iam_role.eks_node_role.arn subnet_ids = local.subnet_ids scaling_config { min_size = 1 max_size = 1 desired_size = 1 } labels = { node_id = "x" } instance_types = ["t3.small"] } resource "aws_eks_node_group" "node_y" { cluster_name = aws_eks_cluster.my_cluster.name node_group_name = "node-y" node_role_arn = data.aws_iam_role.eks_node_role.arn subnet_ids = local.subnet_ids scaling_config { min_size = 1 max_size = 1 desired_size = 1 } labels = { node_id = "y" } instance_types = ["t3.small"] }
上記の内容を$ terraform applyすると、 Node が 2 つ作られる。
$ kubectl get nodes -L node_id NAME STATUS ROLES AGE VERSION NODE_ID ip-10-0-0-10.ap-northeast-1.compute.internal Ready <none> 12m v1.33.5-eks-113cf36 x ip-10-0-1-34.ap-northeast-1.compute.internal Ready <none> 12m v1.33.5-eks-113cf36 y
どちらにも Taint は設定されていない。
$ kubectl get nodes -l node_id=x -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints NAME TAINTS ip-10-0-0-10.ap-northeast-1.compute.internal <none> $ kubectl get nodes -l node_id=y -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints NAME TAINTS ip-10-0-1-34.ap-northeast-1.compute.internal <none>
この状態で以下の内容のmanifest.yamlを用意し$ kubectl apply -f manifest.yamlすると、 Pod が 2 つ作られる。
apiVersion: v1 kind: Pod metadata: name: pod-a spec: containers: - name: busybox image: docker.io/library/busybox:1.36 command: ["sleep", "infinity"] --- apiVersion: v1 kind: Pod metadata: name: pod-b spec: containers: - name: busybox image: docker.io/library/busybox:1.36 command: ["sleep", "infinity"]
この際、pod-aとpod-bがそれぞれどの Node に配置されるかは、制御できない。Kubernetes によって配置されるが、pod-aが node-xでpod-bがnode-yになることもあれば、両方の pod がnode-yに配置されることもある。
Terraform の定義内容のうちnode_xを以下に書き換える。具体的にはtaintフィールドを追加する。NoExecuteではなくNO_EXECUTEと書くので注意する。
resource "aws_eks_node_group" "node_x" { cluster_name = aws_eks_cluster.my_cluster.name node_group_name = "node-x" node_role_arn = data.aws_iam_role.eks_node_role.arn subnet_ids = local.subnet_ids scaling_config { min_size = 1 max_size = 1 desired_size = 1 } labels = { node_id = "x" } taint { key = "key1" value = "value1" effect = "NO_EXECUTE" } instance_types = ["t3.small"] }
$ terraform applyしてから確認するとnode-xに Taint が付与されている。
$ kubectl get nodes -l node_id=x -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints NAME TAINTS ip-10-0-0-10.ap-northeast-1.compute.internal [map[effect:NoExecute key:key1 value:value1]] $ kubectl get nodes -l node_id=y -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints NAME TAINTS ip-10-0-1-34.ap-northeast-1.compute.internal <none>
これでもうnode-xには Pod は配置されなくなるはずである。
$ kubectl delete -f manifest.yamlしてから再度$ kubectl apply -f manifest.yamlをして Pod を作り直す。
そうすると以下のように Pod は 2 つともnode-yに配置される。Pod を何度作り直しても同じ結果になる。
$ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-a 1/1 Running 0 8s 10.0.1.183 ip-10-0-1-34.ap-northeast-1.compute.internal <none> <none> pod-b 1/1 Running 0 8s 10.0.1.82 ip-10-0-1-34.ap-northeast-1.compute.internal <none> <none>
似たような機能としてnodeSelectorがあるがこれは Pod 側が指定するもの。Taint は逆に Node 側が指定する。
Toleration
先程「特定の条件を満たした Pod 以外はその Node に配置されないようになる」と書いたが、その「特定の条件」が Toleration 。
Toleration は Pod の属性。Toleration が設定されている Pod はその設定内容に応じて、 Taint が設定されている Node への配置が特別に許可されるようになる。
設定できるフィールドは 5 つあるが、省略可能なものも多く、全てを設定することは稀。
key- 対象となる Taint の key
- 後述の
operatorがExistsの場合は省略可能
value- 対象となる Taint の value
- 後述の
operatorがExistsの場合は不要
operator- どのようなロジックで対象となる Taint を決定するかを指定する
EqualもしくはExistsを指定するEqualkeyとvalueの両方が一致する Taint を対象とする
Existskeyが指定されている場合、そのkeyを持つ Taint を対象とするkeyが指定されていない場合、すべての Taint を対象とする
effect- どの Effect (
PreferNoScheduleorNoScheduleorNoExecute)の Taint を許容するかを指定する - 省略した場合は全ての Effect を許容したことになる
- どの Effect (
tolerationSecondsNoExecuteの Taint が付与されてから、実際に退去されるまでの猶予時間
こちらも例を示す。
まず、node_yを書き換えてこちらにも Taint を設定する。
resource "aws_eks_node_group" "node_y" { cluster_name = aws_eks_cluster.my_cluster.name node_group_name = "node-y" node_role_arn = data.aws_iam_role.eks_node_role.arn subnet_ids = local.subnet_ids scaling_config { min_size = 1 max_size = 1 desired_size = 1 } labels = { node_id = "y" } taint { key = "key2" value = "value2" effect = "NO_SCHEDULE" } instance_types = ["t3.small"] }
$ terraform applyすると以下の内容になる。
$ kubectl get nodes -l node_id=x -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints NAME TAINTS ip-10-0-0-10.ap-northeast-1.compute.internal [map[effect:NoExecute key:key1 value:value1]] $ kubectl get nodes -l node_id=y -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints NAME TAINTS ip-10-0-1-34.ap-northeast-1.compute.internal [map[effect:NoSchedule key:key2 value:value2]]
node-xもnode-yも Taint が設定されているため、対応する Toleration が設定された Pod しか配置できなくなった。
そしてmanifest.yamlを以下の内容にして$ kubectl apply -f manifest.yamlする。
apiVersion: v1 kind: Pod metadata: name: pod-a spec: tolerations: - key: "key1" value: "value1" operator: "Equal" effect: "NoExecute" tolerationSeconds: 3600 containers: - name: busybox image: docker.io/library/busybox:1.36 command: ["sleep", "infinity"] --- apiVersion: v1 kind: Pod metadata: name: pod-b spec: tolerations: - operator: "Exists" effect: "NoSchedule" containers: - name: busybox image: docker.io/library/busybox:1.36 command: ["sleep", "infinity"] --- apiVersion: v1 kind: Pod metadata: name: pod-c spec: tolerations: - operator: "Exists" effect: "PreferNoSchedule" containers: - name: busybox image: docker.io/library/busybox:1.36 command: ["sleep", "infinity"]
そうすると以下のようになる。
$ kubectl get nodes -L node_id NAME STATUS ROLES AGE VERSION NODE_ID ip-10-0-0-10.ap-northeast-1.compute.internal Ready <none> 102m v1.33.5-eks-113cf36 x ip-10-0-1-34.ap-northeast-1.compute.internal Ready <none> 101m v1.33.5-eks-113cf36 y $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-a 1/1 Running 0 5s 10.0.0.60 ip-10-0-0-10.ap-northeast-1.compute.internal <none> <none> pod-b 1/1 Running 0 5s 10.0.1.82 ip-10-0-1-34.ap-northeast-1.compute.internal <none> <none> pod-c 0/1 Pending 0 5s <none> <none> <none> <none>
pod-aはnode-xに、pod-bはnode-yに配置されている。そしてpod-cはどこにも配置されずPendingのままになっている。
Toleration の内容を確認し、なぜこうなるのかを解説する。
pod-a- Toleration の内容
tolerations: - key: "key1" value: "value1" operator: "Equal" effect: "NoExecute"
Equalなのでkeyとvalueの両方が一致する Taint に配置可能- つまり
key1:value1 - そしてその Taint は
node-xが該当する
- つまり
- effect は
NoExecuteだがそれもnode-xと一致する node-yの Taint は条件が合わない- そのため
node-xに配置される
- Toleration の内容
pod-b- Toleration の内容
tolerations: - operator: "Exists" effect: "NoSchedule"
Existsでkeyが指定されていないので、すべての Taint が対象になるNoScheduleはnode-xは異なりnode-yは一致する- そのため
node-yに配置される
- Toleration の内容
pod-c- Toleration の内容
tolerations: - operator: "Exists" effect: "PreferNoSchedule"
Existsでkeyが指定されていないので、すべての Taint が対象になるPreferNoScheduleはnode-xともnode-yとも異なる- そのためどちらの Node にも配置されない
- Toleration の内容
自動付与
Taint も Toleration も、 Kubernetes によって自動付与されることがある。
例えば先程作成したpod-aの Toleration を見てみる。
$ kubectl get pod pod-a -o json | jq '.spec.tolerations' [ { "effect": "NoExecute", "key": "key1", "operator": "Equal", "tolerationSeconds": 3600, "value": "value1" }, { "effect": "NoExecute", "key": "node.kubernetes.io/not-ready", "operator": "Exists", "tolerationSeconds": 300 }, { "effect": "NoExecute", "key": "node.kubernetes.io/unreachable", "operator": "Exists", "tolerationSeconds": 300 } ]
マニフェストファイルに記載したkey1以外にも 2 つ Toleration があることがわかる。これらが Kubernetes によって自動的に付与されたもの。
Taint も、自動付与されることがある。
それを確認するためにまずnode_xを以下の内容にする。つまりtaintを消す。
resource "aws_eks_node_group" "node_x" { cluster_name = aws_eks_cluster.my_cluster.name node_group_name = "node-x" node_role_arn = data.aws_iam_role.eks_node_role.arn subnet_ids = local.subnet_ids scaling_config { min_size = 1 max_size = 1 desired_size = 1 } labels = { node_id = "x" } instance_types = ["t3.small"] }
これで$ terraform applyすると Taint は何も無くなっている。
$ kubectl get nodes -l node_id=x -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints NAME TAINTS ip-10-0-0-10.ap-northeast-1.compute.internal <none>
次にmanifest.yamlを以下の内容にして$ kubectl apply -f manifest.yamlする。
apiVersion: v1 kind: Pod metadata: name: disk-hog spec: nodeSelector: node_id: "x" containers: - name: busybox image: docker.io/library/busybox:1.36 command: ["/bin/sh"] args: ["-c", "dd if=/dev/zero of=/tmp/bigfile bs=1M count=16000; sleep 3600"]
この Pod はnode-xに配置され、指定された容量だけディスクへの書き込みを行う。
そうすると、ディスク容量の圧迫を意味するnode.kubernetes.io/disk-pressureが付与される。
NoScheduleなので、(対応する Toleration が設定された Pod を除いて)この Node には Pod が配置されない。
NoExecuteではないのでdisk-hogが退去させられることもない。
$ kubectl get nodes -l node_id=x -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints NAME TAINTS ip-10-0-0-10.ap-northeast-1.compute.internal [map[effect:NoSchedule key:node.kubernetes.io/disk-pressure timeAdded:2025-11-04T14:20:00Z]]
$ kubectl delete -f manifest.yamlでdisk-hogを消して暫くすると、 Taint は消える。
$ kubectl get nodes -l node_id=x -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints NAME TAINTS ip-10-0-0-10.ap-northeast-1.compute.internal <none>