🦉

K8sでServiceAccountをPodから確実に取り外す方法

2024/09/09に公開

以前、ServiceAccountを取り外すのと同時に、不要となるServiceAccountリソースを削除しようとしたことがありました。この時はDeploymentからserviceAccountNameフィールドを削除して対応しようとしましたが、ServiceAccountが外れず想定通りにいきませんでした。それを解決するためにServiceAccountの取り外し方について調べたので、今回はそれをまとめました。

ServiceAccountの取り外し方

ServiceAccountをDeploymentやJobsなどのリソースにアタッチするためにはPodテンプレート内のserviceAccountNameフィールドを利用します。しかし、すでにアタッチされているServiceAccountを外したい場合、単にテンプレートからserviceAccountNameフィールドを削除するだけでは外せません。このフィールドを削除しても後方互換性の維持のためにDeploymentやReplicaSetは更新されないためです。

すでにServiceAccountがアタッチされているリソースからServiceAccountを外したい場合は、下記いずれかの対応をする必要があります。

  • serviceAccountNameserviceAccountフィールドにnullを設定してマニフェストを適用する (基本的にこの対応を推奨)
  • serviceAccountNameフィールドにdefaultを設定してマニフェストを適用する
  • kubectl editを用いて、DeploymentやJobsの設定から直接serviceAccountNameフィールドを削除する

1つずつ見ていきましょう。

フィールドにnullを設定する方法

これは公式ドキュメントにも記載されている方法になります。Podテンプレート内のserviceAccountNameserviceAccountフィールドにnullを明示的に設定することで、既存リソースの更新が行われます。通常の設定更新時と同様に、新しい設定のPodが立ち上がり古いPodは削除されます。

The .spec.serviceAccount field is a deprecated alias for .spec.serviceAccountName. If you want to remove the fields from a workload resource, set both fields to empty explicitly on the pod template.

Configure Service Accounts for Pods | Kubernetes

この方法の場合、適用後に2つの不要なフィールドがマニフェストに残ります。しかし、ServiceAccountを外した後ならば、フィールドをマニフェストから削除できるので、最終的なマニフェストの見通しは良くなります。

検証

まずは検証用の環境を用意します。ローカル環境にkindを利用してKubernetesクラスタを立ち上げます。今回利用する環境は下記となります。この環境は後続の検証でも利用します。

$ kind version
kind v0.24.0 go1.22.6 darwin/arm64

$ kind create cluster

$ kubectl version
Client Version: v1.31.0
Kustomize Version: v5.4.2
Server Version: v1.31.0

次に、ServiceAccountをアタッチしたDeploymentを作成します。以下のマニフェストをmanifest.yamlとして保存します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service-account
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      serviceAccountName: my-service-account
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

マニフェストファイルを作成したらkubectl applyコマンドでリソースを作成し、ServiceAccountがアタッチされていることを確認します。

$ kubectl apply -f manifest.yaml
serviceaccount/my-service-account created
deployment.apps/nginx created

$ kubectl get deploy
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   1/1     1            1           39s

$ kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
nginx-5864c46b87-sfcx7   1/1     Running   0          41s

# DeploymentにServiceAccountがアタッチされていることを確認
$ kubectl describe deploy nginx | grep "Service Account"
  Service Account:  my-service-account

# PodにもServiceAccountがアタッチされていることを確認
$ kubectl describe po nginx-5864c46b87-sfcx7 | grep "Service Account"
Service Account:  my-service-account

※1: ここまでの手順は後続の検証でも行います。

ServiceAccountがアタッチされているリソースが作成できたので、次にServiceAccountを外すためにマニフェストを更新します。serviceAccountNameserviceAccountフィールドにnullを設定します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service-account
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      serviceAccountName: null # 変更
      serviceAccount: null # 追加
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

kubectl applyコマンドで更新したマニフェストを適用し、ServiceAccountが外されていることを確認します。

$ kubectl apply -f manifest.yaml
serviceaccount/my-service-account unchanged
deployment.apps/nginx configured

$ kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
nginx-54b9c68f67-7vs2t   1/1     Running   0          22s

# DeploymentからServiceAccountが外されていることを確認
$ kubectl describe deploy nginx | grep "Service Account"
# 何も表示されない

# PodからServiceAccount(my-service-account)が外されていることを確認
# *PodはServiceAccountが無くなることはなく、defaultがアタッチされる。
$ kubectl describe po nginx-54b9c68f67-7vs2t | grep "Service Account"
Service Account:  default

これで、ServiceAccountを問題なく取り外すことができました。

フィールドにdefaultを設定する方法

「フィールドにnullを設定する方法」の検証で確認した通り、serviceAccountNameフィールドを指定しない場合、デフォルトでdefaultServiceAccountが適用されます。ServiceAccountは「なし」にはなりません。
すなわち、「すでに適用しているServiceAccountを外す」は「default ServiceAccountを代わりに適用する」と同じです。これを利用して、serviceAccountNamedefaultを明示的に指定することで、既存のServiceAccountを取り外すことができます。

この方法も「フィールドにnullを設定する方法」と同様に不要なフィールドが一時的に残りますが、のちにそのフィールドをマニフェストから削除することで、最終的なマニフェストの見通しをよくできます。しかし、Kubernetes上のDeploymentやJobsのリソース情報にはdefault ServiceAccountがアタッチされているように表示されます。そのため、ServiceAccountを外したというよりかは、defaultに切り替えたように見えます。(実際のリソースの挙動は外した場合と変わりません)

検証

まず、検証の下準備として、先ほどの検証で行った※1までの手順を行います。

$ kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
nginx-5864c46b87-sg4fg   1/1     Running   0          18s

$ kubectl describe deploy nginx | grep "Service Account"
  Service Account:  my-service-account

$ kubectl describe po nginx-5864c46b87-sg4fg | grep "Service Account"
Service Account:  my-service-account

ServiceAccountがアタッチされているリソースが作成できたので、次にServiceAccountを外すためにマニフェストを更新します。serviceAccountNameフィールドにdefaultを設定します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service-account
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      serviceAccountName: default # 変更
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

更新したマニフェストをkubectl applyコマンドで適用し、ServiceAccountが外されていることを確認します。

$ kubectl apply -f manifest.yaml
serviceaccount/my-service-account unchanged
deployment.apps/nginx configured

$ kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
nginx-54b9c68f67-7vs2t   1/1     Running   0          22s

# DeploymentからServiceAccount(my-service-account)が外されていることを確認
# ServiceAccount情報が無くなるわけではなく、defaultがアタッチされていることになる。
$ kubectl describe deploy nginx | grep "Service Account"
  Service Account:  default

# PodからServiceAccount(my-service-account)が外されていることを確認
k describe po nginx-bd49b995-bbncn | grep "Service Account"
Service Account:  default

PodからServiceAccountが外されていることが確認できました。しかし、前述した通りDeploymentにはService Account: defaultと表示されフィールド自体は残ってしまいます。

kubectl editを用いて直接削除する方法

すでにデプロイされているDeploymentやJobsから直接ServiceAccountを外す方法もあります。kubectl editコマンドを用いて、リソースの設定からserviceAccountNameserviceAccountフィールドを直接削除することで外すことができます。

$ kubectl edit deployment <deployment-name>

この場合、マニフェストファイル側からは単にserviceAccountNameフィールドを削除するだけで良くなります。次回以降のマニフェスト適用時には、すでにリソースとマニフェストからServiceAccountが外されているため、変更が発生しません。また、マニフェストに不要なフィールドが残ることもありません。しかし、kubectl editコマンドを用いるため、手動操作が必要な点はデメリットと言えます。

検証

先ほどと同様にまずは※1までの手順を行います。

$ kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
nginx-5864c46b87-8cwd4   1/1     Running   0          33s

$ kubectl describe deploy nginx | grep "Service Account"
  Service Account:  my-service-account

$ kubectl describe po nginx-5864c46b87-8cwd4 | grep "Service Account"
Service Account:  my-service-account

リソースの準備ができたら、kubectl editコマンドを用いてServiceAccountを外します。(serviceAccountNameserviceAccountフィールドを削除します)

$ kubectl edit deploy nginx
deployment.apps/nginx edited

削除後にはPodが再生成され、ServiceAccountが外されているはずです。

$ kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
nginx-54b9c68f67-fxrz7   1/1     Running   0          19s

# DeploymentからServiceAccountが外されていることを確認
$ kubectl describe deploy nginx | grep "Service Account"
# 何も表示されない

# PodからServiceAccount(my-service-account)が外されていることを確認
$ kubectl describe po nginx-54b9c68f67-fxrz7 | grep "Service Account"
Service Account:  default

なお、すでにDeploymentやPodからServiceAccountが外されているので、下記のようにマニフェストを更新しても変更が発生しません。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service-account
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      # serviceAccountName: my-service-account # 削除
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

kubectl applyコマンドで更新後のマニフェストを適用してもPodが再生成されることはありません。
なお、出力結果にdeployment.apps/nginx configuredと表示されていますが、実リソースには変更が発生していません。これはKubernetesが前回適用されたマニフェストを覚えており、そこから差分(serviceAccountNameフィールドが削除された)が発生しているため表示されています。

$ kubectl apply -f manifest.yaml
serviceaccount/my-service-account unchanged
deployment.apps/nginx configured

$ kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
nginx-54b9c68f67-fxrz7   1/1     Running   0          1m45s

終わりに

ServiceAccountを確実に取り外すための3つの方法を見てきました。
一般的にはKubernetes上のリソース管理にArgoCDなどのCDツールを利用していることが多いはずです。その場合、kubectl editを用いて直接ServiceAccountを外す方法は避けるべきです。そのため、serviceAccountNameserviceAccountフィールドにnullを設定する方法が推奨されます。default ServiceAccountを代わりに適用する方法もありますが、リソースの情報からはServiceAccountが適用されているように見えてしまうので、個人的にはあまりお勧めしません。

Money Forward Developers

Discussion