Имеются у нас Github runners, которые запущены в виде подов в Kubernetes-кластере, см. Github: обзор Github Actions и деплой с ArgoCD.
На них выполняеттся сборка docker-образов и их пуш в Docker Hub, а затем деплой приложения с Helm или ArgoCD.
При первом запуске helm install
в поде получаем ошибку “x509: certificate signed by unknown authority“:
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
А без указания API-сервера – ошибка прав доступа:
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"
Собственно, проблема ясна: Helm в поде пытается достучаться к API-серверу, используя default ServiceAccount, который был создан при деплое Github runner.
Проверка доступа ServiceAccount
Как уже писалось в Kubernetes: ServiceAccounts, JWT-токены, аутентификация и RBAC-авторизация, для аутентификации на API-сервере нам нужны токен и ключ Certificate Authority.
Подключаемся в под:
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.
Создаём переменные:
[email protected]:/actions-runner# CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
[email protected]:/actions-runner# TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
[email protected]:/actions-runner# NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
И пробуем получить доступ к API сейчас:
[email protected]:/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
}
Аналогичную ошибку увидим, если запустим 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"
Хорошо, и что нам делать?
Создание Kubernetes ServiceAccount для Kubernetes Pod
Собственно, вместо того, что бы монтировать к создаваемым подам дефолтный ServiceAccount с его токеном – нам надо создать свой, которому мы дадим доступ к ресурсам API-сервера через Kubernetes RBAC Role и Kubernetes RBAC RoleBinding.
Создание RBAC Role
Роль можно взять дефолтную, из списка User-facing roles, например cluster-admin:
kubectl get clusterrole cluster-admin
NAME CREATED AT
cluster-admin 2020-11-27T14:44:52Z
Или написать свою, с чётким ограничем доступов. К примеру, дадим доступ к 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"]
Создаём эту роль в нужном нам неймспейсе:
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
Проверяем:
kk -n dev-1-18-backend-github-runners-helm-ns get role
NAME CREATED AT
github-runner-deployer-role 2021-09-28T08:05:20Z
Создание ServiceAccount
Далее, создадим сервис-аккаунт в этом неймспейсе:
apiVersion: v1
kind: ServiceAccount
metadata:
name: "github-runner-deployer-sa"
Применяем:
kubectl -n dev-1-18-backend-github-runners-helm-ns apply -f github-runner-deployer-sa.yaml
Создание 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
Применяем её:
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
Подключение 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"
Создаём под:
kubectl -n dev-1-18-backend-github-runners-helm-ns apply -f github-runner-deployer-pod.yaml
pod/github-runners-deployer-pod created
Подключаемся в него:
kubectl -n dev-1-18-backend-github-runners-helm-ns exec -ti github-runners-deployer-pod bash
И проверяем доступ к API:
[email protected]:/# TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
[email protected]:/# NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
[email protected]:/# 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",
...
Теперь доступ к get
и list
подов есть. А вот к Secrets – нет, так как не задано в нашей роли:
[email protected]:/# 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\"",
...
Helm ServiceAccount
Вернёмся к Helm и нашим Github Runners.
Собственно, Helm из подов Github runners будет деплоить всё и везде, так что можно выдать ему админ-права на весь кластер, подключив ClusterRole cluster-admin.
Удаляем созданный выше биндинг:
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
Создаём новый, но теперь с типом 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
Подключаем сервис-аккаунт к этой кластер-роли:
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
Возвращаемся в под, и пробуем получить доступ к секретам теперь:
[email protected]:/# 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",
...
Всё – теперь наш Helm получил полный доступ к кластеру.
Осталось обновить Deployment для Github runners, и задать там новый ServiceAccount.
Редактируем его:
kubectl -n dev-1-18-backend-github-runners-helm-ns edit deploy actions-runner-deployment
Задаём новый SA – указываем serviceAccount
:

Ждём, пока поды пересоздадутся (см. Kubernetes: ConfigMap и Secrets — auto-reload данных в подах), подключаемся, и проверяем:
Доступ к другим неймспейсам – ведь мы создавали ClusterRoleBinding, который применяется ко всему кластеру, а не отдельному namespace, как в случае с обычным RoleBinding:
[email protected]:/actions-runner# kubectl auth can-i list pods --namespace istio-system
yes
Отлично – в неймспейс istio-system тоже полный доступ есть.
И доступ самого Helm:
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
Готово.