Kubernetes: Helm – “x509: certificate signed by unknown authority” и ServiceAccount для Pod

Автор: | 28/09/2021

Имеются у нас Github runners, которые запущены в виде подов в Kubernetes-кластере, см. Github: обзор Github Actions и деплой с ArgoCD.

На них выполняеттся сборка docker-образов и их пуш в Docker Hub, а затем деплой приложения с Helm или ArgoCD.

При первом запуске helm install в поде получаем ошибку “x509: certificate signed by unknown authority“:

[simterm]

# helm --kube-apiserver=https://kubernetes.default.svc.cluster.local list
Error: Kubernetes cluster unreachable: Get "https://kubernetes.default.svc.cluster.local/version?timeout=32s": x509: certificate signed by unknown authority

[/simterm]

А без указания API-сервера – ошибка прав доступа:

[simterm]

# helm list
Error: list: failed to list: secrets is forbidden: User "system:serviceaccount:dev-1-18-backend-github-runners-helm-ns:default" cannot list resource "secrets" in API group "" in the namespace "dev-1-18-backend-github-runners-helm-ns"

[/simterm]

Собственно, проблема ясна: Helm в поде пытается достучаться к API-серверу, используя default ServiceAccount, который был создан при деплое Github runner.

Проверка доступа ServiceAccount

Как уже писалось в Kubernetes: ServiceAccounts, JWT-токены, аутентификация и RBAC-авторизация, для аутентификации на API-сервере нам нужны токен и ключ Certificate Authority.

Подключаемся в под:

[simterm]

$ kk -n dev-1-18-backend-github-runners-helm-ns exec -ti actions-runner-deployment-7f78968949-tmrtt bash
Defaulting container name to runner.
Use 'kubectl describe pod/actions-runner-deployment-7f78968949-tmrtt -n dev-1-18-backend-github-runners-helm-ns' to see all of the containers in this pod.
root@actions-runner-deployment-7f78968949-tmrtt:/actions-runner#

[/simterm]

Создаём переменные:

[simterm]

root@actions-runner-deployment-7f78968949-tmrtt:/actions-runner# CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
root@actions-runner-deployment-7f78968949-tmrtt:/actions-runner# TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
root@actions-runner-deployment-7f78968949-tmrtt:/actions-runner# NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)

[/simterm]

И пробуем получить доступ к API сейчас:

[simterm]

root@actions-runner-deployment-7f78968949-tmrtt:/actions-runner# curl -s --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/$NAMESPACE/pods" | jq '{message, code}'
{
  "message": "pods is forbidden: User \"system:serviceaccount:dev-1-18-backend-github-runners-helm-ns:default\" cannot list resource \"pods\" in API group \"\" in the namespace \"dev-1-18-backend-github-runners-helm-ns\"",
  "code": 403
}

[/simterm]

Аналогичную ошибку увидим, если запустим kubectl get pod сейчас из этого пода:

[simterm]

root@actions-runner-deployment-7f78968949-tmrtt:/actions-runner# kubectl get pod
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:dev-1-18-backend-github-runners-helm-ns:default" cannot list resource "pods" in API group "" in the namespace "dev-1-18-backend-github-runners-helm-ns"

[/simterm]

Хорошо, и что нам делать?

Создание Kubernetes ServiceAccount для Kubernetes Pod

Собственно, вместо того, что бы монтировать к создаваемым подам дефолтный ServiceAccount с его токеном – нам надо создать свой, которому мы дадим доступ к ресурсам API-сервера через Kubernetes RBAC Role и Kubernetes RBAC  RoleBinding.

Создание RBAC Role

Роль можно взять дефолтную, из списка User-facing roles, например cluster-admin:

[simterm]

$ kubectl get clusterrole cluster-admin
NAME            CREATED AT
cluster-admin   2020-11-27T14:44:52Z

[/simterm]

Или написать свою, с чётким ограничем доступов. К примеру, дадим доступ к verb (действия) get и list на resources pod:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
   name: "github-runner-deployer-role"
rules:
 - apiGroups: [""]
   resources: ["pods"]
   verbs: ["get", "list"]

Создаём эту роль в нужном нам неймспейсе:

[simterm]

$ kubectl -n dev-1-18-backend-github-runners-helm-ns apply -f github-runner-deployer-role.yaml 
role.rbac.authorization.k8s.io/github-runner-deployer-role created

[/simterm]

Проверяем:

[simterm]

$ kk -n dev-1-18-backend-github-runners-helm-ns get role
NAME                          CREATED AT
github-runner-deployer-role   2021-09-28T08:05:20Z

[/simterm]

Создание ServiceAccount

Далее, создадим сервис-аккаунт в этом неймспейсе:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: "github-runner-deployer-sa"

Применяем:

[simterm]

$ kubectl -n dev-1-18-backend-github-runners-helm-ns apply -f github-runner-deployer-sa.yaml

[/simterm]

Создание RoleBinding

Теперь, надо создать binding – связь между этим ServiceAccount и созданной ранее ролью.

Создаём RoleBinding:

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
   name: "github-runner-deployer-rolebinding"
subjects:
 - kind: ServiceAccount
   name: "github-runner-deployer-sa"
   namespace: "dev-1-18-backend-github-runners-helm-ns"
roleRef:
   kind: Role
   name: "github-runner-deployer-role"
   apiGroup: rbac.authorization.k8s.io

Применяем её:

[simterm]

$ kk -n dev-1-18-backend-github-runners-helm-ns apply -f github-runner-deployer-rolebinding.yaml 
rolebinding.rbac.authorization.k8s.io/github-runner-deployer-rolebinding created

[/simterm]

Подключение ServiceAccount к Kubernetes Pod

И последним осталось подключить созданный ServiceAccount к нашим подам.

Можно проверить через создание нового статичного пода, без деплоймента и скейлинга:

apiVersion: v1
kind: Pod
metadata:
  name: "github-runners-deployer-pod"
spec:
  containers:
    - name: "github-runners-deployer"
      image: "nginx"
      ports:
        - name: "web"
          containerPort: 80
          protocol: TCP
  serviceAccountName: "github-runner-deployer-sa"

Создаём под:

[simterm]

$ kubectl -n dev-1-18-backend-github-runners-helm-ns apply -f github-runner-deployer-pod.yaml 
pod/github-runners-deployer-pod created

[/simterm]

Подключаемся в него:

[simterm]

$ kubectl -n dev-1-18-backend-github-runners-helm-ns exec -ti github-runners-deployer-pod bash
root@github-runners-deployer-pod:/#

[/simterm]

И проверяем доступ к API:

[simterm]

root@github-runners-deployer-pod:/# CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
root@github-runners-deployer-pod:/# TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
root@github-runners-deployer-pod:/# NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
oot@github-runners-deployer-pod:/# curl -s --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/$NAMESPACE/pods"
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/namespaces/dev-1-18-backend-github-runners-helm-ns/pods",
    "resourceVersion": "251020450"
  },
  "items": [
    {
      "metadata": {
        "name": "actions-runner-deployment-7f78968949-jsh6l",
        "generateName": "actions-runner-deployment-7f78968949-",
        "namespace": "dev-1-18-backend-github-runners-helm-ns",
...

[/simterm]

Теперь доступ к get и list подов есть. А вот к Secrets – нет, так как не задано в нашей роли:

[simterm]

root@github-runners-deployer-pod:/# curl -s --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/$NAMESPACE/secrets"
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": "secrets is forbidden: User \"system:serviceaccount:dev-1-18-backend-github-runners-helm-ns:github-runner-deployer-sa\" cannot list resource \"secrets\" in API group \"\" in the namespace \"dev-1-18-backend-github-runners-helm-ns\"",
...

[/simterm]

Helm ServiceAccount

Вернёмся к Helm и нашим Github Runners.

Собственно, Helm из подов Github runners будет деплоить всё и везде, так что можно выдать ему админ-права на весь кластер, подключив ClusterRole cluster-admin.

Удаляем созданный выше биндинг:

[simterm]

$ kubectl -n dev-1-18-backend-github-runners-helm-ns delete rolebinding github-runner-deployer-rolebinding
rolebinding.rbac.authorization.k8s.io "github-runner-deployer-rolebinding" deleted

[/simterm]

Создаём новый, но теперь с типом ClusterRoleBinding, что бы дать права на все неймспейсы кластера:

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
   name: "github-runner-deployer-cluster-rolebinding"
subjects:
 - kind: ServiceAccount
   name: "github-runner-deployer-sa"
   namespace: "dev-1-18-backend-github-runners-helm-ns"
roleRef:
   kind: ClusterRole
   name: "cluster-admin"
   apiGroup: rbac.authorization.k8s.io

Подключаем сервис-аккаунт к этой кластер-роли:

[simterm]

$ kubectl -n dev-1-18-backend-github-runners-helm-ns apply -f github-runner-deployer-clusterrolebinding.yaml 
clusterrolebinding.rbac.authorization.k8s.io/github-runner-deployer-rolebinding created

[/simterm]

Возвращаемся в под, и пробуем получить доступ к секретам теперь:

[simterm]

root@github-runners-deployer-pod:/# curl -s --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" "https://kubernetes.default.svc.cluster.local/api/v1/namespaces/$NAMESPACE/secrets"
{
  "kind": "SecretList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/namespaces/dev-1-18-backend-github-runners-helm-ns/secrets",
    "resourceVersion": "251027845"
  },
  "items": [
    {
      "metadata": {
        "name": "bttrm-docker-secret",
        "namespace": "dev-1-18-backend-github-runners-helm-ns",
        "selfLink": "/api/v1/namespaces/dev-1-18-backend-github-runners-helm-ns/secrets/bttrm-docker-secret",
...

[/simterm]

Всё – теперь наш Helm получил полный доступ к кластеру.

Осталось обновить Deployment для Github runners, и задать там новый ServiceAccount.

Редактируем его:

[simterm]

$ kubectl -n dev-1-18-backend-github-runners-helm-ns edit deploy actions-runner-deployment

[/simterm]

Задаём новый SA – указываем serviceAccount:

Ждём, пока поды пересоздадутся (см. Kubernetes: ConfigMap и Secrets — auto-reload данных в подах), подключаемся, и проверяем:

[simterm]

root@actions-runner-deployment-6dfc9b457f-mc7rt:/actions-runner# kubectl auth can-i list pods
yes

[/simterm]

Доступ к другим неймспейсам – ведь мы создавали ClusterRoleBinding, который применяется ко всему кластеру, а не отдельному namespace, как в случае с обычным RoleBinding:

[simterm]

root@actions-runner-deployment-6dfc9b457f-mc7rt:/actions-runner# kubectl auth can-i list pods --namespace istio-system
yes

[/simterm]

Отлично – в неймспейс istio-system тоже полный доступ есть.

И доступ самого Helm:

[simterm]

root@actions-runner-deployment-6dfc9b457f-mc7rt:/actions-runner# helm list
NAME            NAMESPACE                               REVISION        UPDATED                                         STATUS          CHART                           APP VERSION
github-runners  dev-1-18-backend-github-runners-helm-ns 2               2021-09-22 19:50:18.828686642 +0300 +0300       deployed        github-runners-1632329415       v1.0.0

[/simterm]

Готово.