Мы тут в Cozystack в очередной раз решаем проблему курицы и яйца: как задеплоить CNI и kube-proxy через Flux, но при этом обеспечить работу самого flux без CNI и kube-proxy.

Сам Flux запустить без CNI и kube-proxy можно используя проект flux-aio (от создателя Flux), который запускает единый deployment со всеми контроллерами настроенными на коммуникацию друг с другом через localhost.

Специфика Cozystack заключается в том, что на каждый кластер мы деплоим внутри небольшой HTTP-сервер с Helm-чартами и другими ассетами используемыми в платформе. Flux эти чарты читает и устанавливает в систему.

Но вот как организовать доступ флюксу к внутреннему HTTP-серверу, запущенному как под внутри того же кластера?
Очевидно что без CNI и kube-proxy обратиться к этому поду по персистентному имени он не сможет (coredns тоже зависит от CNI и kube-proxy)

Было несколько вариантов, вроде подселить наш HTTP-сервер сайдкаром к Flux, или запинить его через nodeAffinity к той же ноде и заставить Flux обращаться на localhost. Но @lllamnyp предложил и более изящное решение - это зароутить Flux через Kubernetes API.

Идея сразу показалась мне хорошей, т.к. закрывает в том числе и потребность в открытом порте на ноде (хотя позже оказалось что это не так).

Таким образом, мы запускаем под cozystack-assets-0 и можем получить доступ к его содержимому по

https://example.org:6443/api/v1/namespaces/cozy-system/pods/cozystack-assets-0/proxy

Но вот незадача, нам нужно как-то авторизоваться, иначе kubernetes api-server нас не пропустит.
В теории можно было бы выделить отдельный ServiceAccount и токен для него, Но Flux не умеет подсовывать заголовки, и вообще не поддерживат что-либо кроме basic-http-auth или mTLS.

Что натолкнуло меня на идею, а почему бы не получить клиентский сертификат для Flux. Благо для этого нам не нужен cert-manager и вообще какой-либо доступ к Kubernetes CA.

И тут мы знакомимся с тем как работает механизм получения клиентских сертификатов в Kubernetes, как оказалось всё уже придумано за нас:

# Создаём приватный ключ и CSR
openssl genrsa -out tls.key 2048
openssl req -new -key tls.key -subj "/CN=cozystack-assets-reader" -out tls.csr

# регистрируем CSR в Kubernetes
kubectl apply -f - <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: cozystack-assets-reader
spec:
  signerName: kubernetes.io/kube-apiserver-client
  request: $(base64 < tls.csr | tr -d '\n')
  usages:
    - client auth
EOF

# аппрувим его
kubectl certificate approve cozystack-assets-reader

# забираем готовый сертификат, подписанный CA нашего Kuberetes кластера
kubectl get csr cozystack-assets-reader \
  -o jsonpath='{.status.certificate}' | base64 -d > tls.crt

# забираем CA сертификат
kubectl get -n kube-public configmap kube-root-ca.crt \
  -o jsonpath='{.data.ca\.crt}' > ca.crt

# создаём секрет для флакса
kubectl create secret generic "cozystack-assets-tls" \
  --namespace='cozy-system' \
  --type='kubernetes.io/tls' \
  --from-file=tls.crt \
  --from-file=tls.key \
  --from-file=ca.crt

Добавим роль:

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cozystack-assets-reader
  namespace: cozy-system
rules:
  - apiGroups: [""]
    resources:
      - pods/proxy
    resourceNames:
      - cozystack-assets-0
    verbs:
      - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cozystack-assets-reader
  namespace: cozy-system
subjects:
  - kind: User
    name: cozystack-assets-reader
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: cozystack-assets-reader
  apiGroup: rbac.authorization.k8s.io

Теперь этот секрет можно использовать для доступа к нашему серверу, прямо через Kubernetes API, в спеке HelmRepository мы указываем:

apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: cozystack-apps
spec:
  url: https://example.org:6443/api/v1/namespaces/cozy-system/pods/cozystack-assets-0/proxy/repos/extra
  certSecretRef:
    name: cozystack-assets-tls

и теперь Flux может скачать все необходимые ассеты.
На мой взгяд это красиывый хак о котором хочется рассказать, т.к. он учит нас чему-то новому. Но я бы не советовал рассматривать эту идею как best practice.

Думаю, что в дальнейшем мы от него избавимся. Сейчас мы постепенно переходим на source-watcher и возможность хранить артефакты сразу в OCIRepository. Таким образом Flux будет качать и собирать все необходимые артефакты напрямую из указанного OCI-образа или Git-репозитория.

Полный код PR'а вы можете посмотреть (и поревьювить) тут:

Комментарии (2)