Advent Calendar のおかげでなんとか年 1 ペースでの記事投稿を維持してます。
これは OpenShift Advent Calendar 2024 の 12/8 の記事です。
前置き
去年は Red Hat Advent Calendar に、 ローカル環境の Podman で Kubernetes にデプロイするためのマニフェストも動作検証しちゃおうという記事を書きました。
ローカルで大体動くことが確認できたら、次は OpenShift にデプロイしたいですね。
例えば Podman で検証済みの次のような Deployment でアプリケーションをデプロイしてみます。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: sample-service-1
environment: openshift
name: sample-service-1
namespace: scc-test
spec:
replicas: 1
selector:
matchLabels:
app: sample-service-1
template:
metadata:
labels:
app: sample-service-1
spec:
serviceAccountName: sample-service-1
containers:
- image: quay.io/kshiraka/sample-service-1:latest
imagePullPolicy: IfNotPresent
name: sample-service-1
ports:
- containerPort: 8080
name: http
protocol: TCP
resources:
limits:
memory: 1Gi
requests:
cpu: 500m
memory: 500Mi
securityContext:
privileged: true
runAsUser: 0
$ oc apply -f deployment.yaml
さて、Deployment を作成したのでアプリケーションの Pod が実行されていることを確認してみます。...おや? Pod が作成されていません...なぜ...
$ oc get pod
No resources found in scc-test namespace.
トラブルシューティングをしていきます。まずは Deployment の詳細を確認してみましょう。
なるほど、 Conditions
の内容から ReplicaSet は作成したものの、何らかの理由で Pod が作成できていないようです。
$ oc describe deployment sample-service-1
~省略~
Conditions:
Type Status Reason
---- ------ ------
Progressing True NewReplicaSetCreated
Available False MinimumReplicasUnavailable
ReplicaFailure True FailedCreate
OldReplicaSets: <none>
NewReplicaSet: sample-service-1-6888568fdc (0/1 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 25s deployment-controller Scaled up replica set sample-service-1-6888568fdc to 1
原因特定のため、次に ReplicaSet の詳細を確認します。
$ oc describe replicaset sample-service-1-6888568fdc
~省略~
Conditions:
Type Status Reason
---- ------ ------
ReplicaFailure True FailedCreate
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedCreate 5s (x15 over 87s) replicaset-controller Error creating: pods "sample-service-1-6888568fdc-" is forbidden: unable to validate against any security context constraint: [provider "anyuid": Forbidden: not usable by user or serviceaccount, provider restricted-v2: .containers[0].runAsUser: Invalid value: 0: must be in the ranges: [1000810000, 1000819999], provider restricted-v2: .containers[0].privileged: Invalid value: true: Privileged containers are not allowed, provider "restricted": Forbidden: not usable by user or serviceaccount, provider "nonroot-v2": Forbidden: not usable by user or serviceaccount, provider "nonroot": Forbidden: not usable by user or serviceaccount, provider "hostmount-anyuid": Forbidden: not usable by user or serviceaccount, provider "machine-api-termination-handler": Forbidden: not usable by user or serviceaccount, provider "hostnetwork-v2": Forbidden: not usable by user or serviceaccount, provider "hostnetwork": Forbidden: not usable by user or serviceaccount, provider "hostaccess": Forbidden: not usable by user or serviceaccount, provider "node-exporter": Forbidden: not usable by user or serviceaccount, provider "privileged": Forbidden: not usable by user or serviceaccount]
pods "sample-service-1-6888568fdc-" is forbidden
となっており、その理由として unable to validate against any security context constraint: ...
と書いてあります。
そう! Kubernetes に慣れ親しんだ開発者の多くが OpenShift を触り始めたときにぶつかるであろう壁、みんな大好き Security Context Constraint (SCC) が Pod を起動できない理由なのでした (タイトルでネタバレしてますね)。
SCC ってなに?
Pod / コンテナに許可する権限を制限・管理する OpenShift 独自の機能です。
Kubernetes はデフォルトの設定では Pod に与える設定や権限のチェックを特に行いません。
一方、OpenShift は基本的にデフォルトでセキュアな設定になっており、セキュリティ的なリスクのある設定や権限を付与したアプリケーションを実行できないしくみになっています。
そのしくみを実現する機能が SCC です。
改めて今回作成した Deployment を見てみると、以下の部分でセキュリティ的にリスクのある特権モードでコンテナを実行しようとしている事が分かります。特権コンテナはホストOSのデバイスやファイルシステムにフルアクセスできる非常にリスクの高い設定であるため、OpenShift は SCC によりこのような設定を行ったアプリケーションをデフォルトで実行できないようにしています。
spec:
template:
spec:
containers:
securityContext: # 特権ユーザ設定を追加
privileged: true # 特権モードを有効にする
runAsUser: 0 # root ユーザ(UID=0)として実行
上の例は明らかにセキュリティ上リスクのある例ですが、例えば特定の UID でプロセスを実行することが前提となっているアプリケーションでは securityContext.runAsUser
で UID を指定したくなります。
しかし、OpenShift がデフォルトで適用する SCC の制約ではこれも制限されており、 OpenShift により割り当てられる任意の UID を使用してアプリケーションを実行することが推奨されます。
SCC の種類
SCC は OpenShift 独自の API リソースとして実装されており、制約の異なる複数の SCC を作成できます。
oc get scc
と実行してみると、デフォルトで用意されている SCC とそれらがどのような設定を制限する定義となっているかの概要が確認できます (例えば PRIV
が false
の場合 securityContext.privileged: true
は設定できない)。
$ oc get scc
NAME PRIV CAPS SELINUX RUNASUSER FSGROUP SUPGROUP PRIORITY READONLYROOTFS VOLUMES
anyuid false <no value> MustRunAs RunAsAny RunAsAny RunAsAny 10 false ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
hostaccess false <no value> MustRunAs MustRunAsRange MustRunAs RunAsAny <no value> false ["configMap","csi","downwardAPI","emptyDir","ephemeral","hostPath","persistentVolumeClaim","projected","secret"]
hostmount-anyuid false <no value> MustRunAs RunAsAny RunAsAny RunAsAny <no value> false ["configMap","csi","downwardAPI","emptyDir","ephemeral","hostPath","nfs","persistentVolumeClaim","projected","secret"]
hostnetwork false <no value> MustRunAs MustRunAsRange MustRunAs MustRunAs <no value> false ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
hostnetwork-v2 false ["NET_BIND_SERVICE"] MustRunAs MustRunAsRange MustRunAs MustRunAs <no value> false ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
machine-api-termination-handler false <no value> MustRunAs RunAsAny MustRunAs MustRunAs <no value> false ["downwardAPI","hostPath"]
node-exporter true <no value> RunAsAny RunAsAny RunAsAny RunAsAny <no value> false ["*"]
nonroot false <no value> MustRunAs MustRunAsNonRoot RunAsAny RunAsAny <no value> false ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
nonroot-v2 false ["NET_BIND_SERVICE"] MustRunAs MustRunAsNonRoot RunAsAny RunAsAny <no value> false ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
privileged true ["*"] RunAsAny RunAsAny RunAsAny RunAsAny <no value> false ["*"]
restricted false <no value> MustRunAs MustRunAsRange MustRunAs RunAsAny <no value> false ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
restricted-v2 false ["NET_BIND_SERVICE"] MustRunAs MustRunAsRange MustRunAs RunAsAny <no value> false ["configMap","csi","downwardAPI","emptyDir","ephemeral","persistentVolumeClaim","projected","secret"]
特に SCC に関する設定をなにもしていない場合、 Pod にはデフォルト SCC のうち以下のように最も限定された権限のみを許可する restricted-v2
SCC を適用します。
- 特権を許可しない
- OpenShift により割り当てられる任意の UID でコンテナプロセスを実行する
- hostPath を許可しない
- ホストの Network やポートへのアクセスを許可しない
SCC が制約する設定についての詳細は以下のように oc describe scc
実行結果の Settings
から確認できます。
$ oc describe scc restricted-v2
Name: restricted-v2
Priority: <none>
Access:
Users: <none>
Groups: <none>
Settings:
Allow Privileged: false
Allow Privilege Escalation: false
Default Add Capabilities: <none>
Required Drop Capabilities: ALL
Allowed Capabilities: NET_BIND_SERVICE
Allowed Seccomp Profiles: runtime/default
Allowed Volume Types: configMap,csi,downwardAPI,emptyDir,ephemeral,persistentVolumeClaim,projected,secret
Allowed Flexvolumes: <all>
Allowed Unsafe Sysctls: <none>
Forbidden Sysctls: <none>
Allow Host Network: false
Allow Host Ports: false
Allow Host PID: false
Allow Host IPC: false
Read Only Root Filesystem: false
Run As User Strategy: MustRunAsRange
UID: <none>
UID Range Min: <none>
UID Range Max: <none>
SELinux Context Strategy: MustRunAs
User: <none>
Role: <none>
Type: <none>
Level: <none>
FSGroup Strategy: MustRunAs
Ranges: <none>
Supplemental Groups Strategy: RunAsAny
Ranges: <none>
結局どうすれば Pod を実行できる?
SCC の制約に引っかかったときの対処方法は以下のどちらかです。
- 適用される SCC の制約の範囲内で Pod を実行できるようアプリケーションの設定や実装を変更する
- アプリケーションの実行に必要な設定を許容する SCC を適用する
前者の対応ができるならそれが望ましいですが、できない場合は後者の対応が必要です。
SCC は Pod を実行する Service Account に対して RBAC により割り当てます。前置きの例を使って説明します。
アプリケーション sample-service-1
は実は特権は不要だったのですが特定のユーザ (UID 1000) での実行が必要だったため、以下のように設定を変更しました。
spec:
template:
spec:
containers:
securityContext:
runAsUser: 1000 # UID=1000として実行
runAsUser
で特定の UID を指定してコンテナを実行することは restricted-v2
SCC で許可されないため、root 以外のユーザでの実行を許容する nonroot-v2
SCC を適用したいです。
SCC の割り当てには以下のコマンドが利用できます。
$ oc adm policy add-scc-to-user <scc_name> -z <serviceaccount_name>
nonroot-v2
SCC を Service Account sample-service-1
に割り当てる場合は以下を実行します。
$ oc adm policy add-scc-to-user nonroot-v2 -z sample-service-1
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:nonroot-v2 added: "sample-service-1"
実際に割り当たっていることを確認してみましょう。各 SCC には以下のような ClusterRole が作成されています。
$ oc get clusterrole system:openshift:scc:nonroot-v2 -oyaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
~省略~
name: system:openshift:scc:nonroot-v2
rules:
- apiGroups:
- security.openshift.io
resourceNames:
- nonroot-v2
resources:
- securitycontextconstraints
verbs:
- use
コマンド実行により ClusterRole を Service Account に割り当てる ClusterRoleBinding が作成されていることが確認できます。
$ oc get clusterrolebinding system:openshift:scc:nonroot-v2 -oyaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
creationTimestamp: "2024-12-08T16:39:07Z"
name: system:openshift:scc:nonroot-v2
resourceVersion: "4861406"
uid: ecb30477-2db7-4a9d-af86-253f90f35af7
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:openshift:scc:nonroot-v2
subjects:
- kind: ServiceAccount
name: sample-service-1
namespace: scc-test
RBAC による割り当て設定が確認できたので、 nonroot-v2
SCC が適用され Pod が実行できたか確認してみましょう。
$ oc get pod
NAME READY STATUS RESTARTS AGE
sample-service-1-66b9fb868c-qtn6z 1/1 Running 0 16m
無事 Pod が起動しています!よかったですね。
Pod にたいしてどの SCC が適用されているかはアノテーション openshift.io/scc
の値から確認できます。
$ oc get pod sample-service-1-66b9fb868c-qtn6z -o jsonpath='{.metadata.annotations.openshift\.io\/scc}'
nonroot-v2
SCC の適用順序
Service Account に対して複数の SCC が割り当てられている場合、以下の適用順序により SCC が選択されます。
- ワークロードに SCC が指定されている場合は指定した SCC を適用
- Service Account に割り当てられた SCC をリスト
- SCC の priority フィールドの値が大きい順にソート
- 3 で順序が決定しない場合は権限が少ない順にソート
- 4 で順序が決定しない場合は名前順でソート
- ソートされた SCC 順に Pod のフィールドを検証し、最初にマッチした SCC を適用
すでに適用する SCC が決まってる場合や専用のカスタム SCC を作成する場合は 1 で SCC を設定することが確実です。アノテーション openshift.io/required-scc
により設定できます。
まとめ
SCC はちょっぴりややこしくて邪険にされがちだけど、OpenShift を守ってくれるいいやつなので嫌いにならないであげてください。