В предыдущем посте ArgoCD: обзор, запуск, настройка SSL, деплой приложения потрогали ArgoCD, запустили тестовый инстанс, и задеплоили приложение из его готовых примеров.
Но наша цель – деплоить наши Helm-чарты, а потому посмотрим, как это можно сделать.
Самое интересное ожидаемо коснулось работы с Helm secrets. Пришлось покостылить, но в результате всё заработало так, как и ожидалось.
Содержание
ArgCD: деплой Helm-чарта
Создаём тестовый чарт:
[simterm]
$ helm create test-helm-chart Creating test-helm-chart
[/simterm]
Проверяем его локально:
[simterm]
$ helm upgrade --install --namespace dev-1-test-helm-chart-ns --create-namespace test-helm-chart-release test-helm-chart/ --debug --dry-run ... {} NOTES: 1. Get the application URL by running these commands: export POD_NAME=$(kubectl get pods --namespace dev-1-test-helm-chart-ns -l "app.kubernetes.io/name=test-helm-chart,app.kubernetes.io/instance=test-helm-chart-release" -o jsonpath="{.items[0].metadata.name}") export CONTAINER_PORT=$(kubectl get pod --namespace dev-1-test-helm-chart-ns $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") echo "Visit http://127.0.0.1:8080 to use your application" kubectl --namespace dev-1-test-helm-chart-ns port-forward $POD_NAME 8080:$CONTAINER_PORT
[/simterm]
Хорошо – чарт работает, пушим в репозиторий.
ArgoCD: подключение приватного репозитория
Github SSH-ключ
У нас Github-организация. Позже создадим отдельного пользователя для ArgoCD, у которого будет свой RSA-ключ доступа, пока добавим новый ключ своему Github-юзеру.
В общем-то можно сделать и через HTTPS и логин:токен, но через ключ уже привычнее.
Генерируем ключ:
[simterm]
$ ssh-keygen -f ~/.ssh/argocd-github-key Generating public/private rsa key pair. ...
[/simterm]
Добавляем его в Github – Settings > SSH keys:
ArgoCD repositories
Переходим в Settings – Reposistories:
Выбираем Connect repo using SSH:
Задаём имя, URL, приватный ключ:
Ключ будет сохранён в Kubernetes Secrets:
[simterm]
$ kk -n dev-1-devops-argocd-ns get secrets NAME TYPE DATA AGE argocd-application-controller-token-mc457 kubernetes.io/service-account-token 3 45h argocd-dex-server-token-74r75 kubernetes.io/service-account-token 3 45h argocd-secret Opaque 5 45h argocd-server-token-54mfx kubernetes.io/service-account-token 3 45h default-token-6mmr5 kubernetes.io/service-account-token 3 45h repo-332507798 Opaque 1 13m
[/simterm]
repo-332507798 – вот он.
Жмём Connect.
Создание приложения
Создаём новое приложение:
Задаём имя, проект оставляем default, в Sync Policy можно включить опцию Auto-create namespace:
В Source оставляем Git, задаём URL репозитория, в Revision указываем бранч, в Path – путь к чарту.
В данном случае репозиторий devops-kubernetes, каталог с чартом – tests/test-helm-chart/, причём ArgoCD сам просканирует репозиторий, и предложит выбор каталогов:
В Destination выбираем локальный (в нашем случае) Kubernetes-кластер, указываем namespace, в который будем деплоить чарт:
В Destination, вместо Directory – указываем Helm, хотя Argo сам увидел, что это каталог с Helm-чартом, выбрал соответствующий тип и подгрузил значения из values.yaml
в корне чарта, тут можно пока ничего не менять – позже в Values Files добавим наш secrets.yaml
:
Готово:
Если кликнуть сейчас по приложению, то видим, что ArgoCD уже просканировал файлы шаблонов, и отобразил компоненты, которые будут задеплоены:
Кликаем Sync, и видим доступные опции – и Prune, с удалением, и Dry Run:
Кликаем Syncronize – пошёл деплой:
Всё задеплоилось и запустилось:
Проверим список приложений:
[simterm]
$ argocd app list NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET guestbook https://kubernetes.default.svc default default Synced Healthy <none> <none> https://github.com/argoproj/argocd-example-apps.git guestbook HEAD test-helm-chart https://kubernetes.default.svc dev-1-devops-test-helm-chart-ns default Synced Healthy <none> <none> [email protected]:***/devops-kubernetes.git tests/test-helm-chart DVPS-458-ArgoCD
[/simterm]
И под в неймспейсе:
[simterm]
$ kubectl -n dev-1-devops-test-helm-chart-ns get pod NAME READY STATUS RESTARTS AGE test-helm-chart-67dccc9fb4-2m5rf 1/1 Running 0 2m27s
[/simterm]
А теперь приступим к работе с Helm secrets.
ArgoCD и Helm Secrets
Всё хорошо и просто, пока мы не дошли до секретов, т.к. Helm в ArgoCD не имеет предустановленных плагинов для Helm.
Из вариантов – собирать свой образ, рекомендовано министерством argoхранения тут>>>, либо – устанавливать в Kubernetes InitContainer через shared-volume, как описано тут>>>.
InitContainer с shared-volume
Первым вариантом я попробовал InitContainer с shared-volume, и в целом оно-то работает – плагин установился.
Выглядел Deployment для argocd-repo-server так:
--- apiVersion: apps/v1 kind: Deployment metadata: labels: app.kubernetes.io/component: repo-server app.kubernetes.io/name: argocd-repo-server app.kubernetes.io/part-of: argocd name: argocd-repo-server spec: selector: matchLabels: app.kubernetes.io/name: argocd-repo-server template: metadata: labels: app.kubernetes.io/name: argocd-repo-server spec: automountServiceAccountToken: false initContainers: - name: argo-tools image: alpine/helm command: [sh, -c] args: - apk add git && apk add curl && apk add bash && helm plugin install https://github.com/futuresimple/helm-secrets volumeMounts: - mountPath: /root/.local/share/helm/plugins/ name: argo-tools containers: - command: - uid_entrypoint.sh - argocd-repo-server - --redis - argocd-redis:6379 image: argoproj/argocd:v1.7.9 imagePullPolicy: Always name: argocd-repo-server ports: - containerPort: 8081 - containerPort: 8084 readinessProbe: initialDelaySeconds: 5 periodSeconds: 10 tcpSocket: port: 8081 volumeMounts: - mountPath: /app/config/ssh name: ssh-known-hosts - mountPath: /app/config/tls name: tls-certs - mountPath: /app/config/gpg/source name: gpg-keys - mountPath: /app/config/gpg/keys name: gpg-keyring - mountPath: /home/argocd/.local/share/helm/plugins/ name: argo-tools volumes: - configMap: name: argocd-ssh-known-hosts-cm name: ssh-known-hosts - configMap: name: argocd-tls-certs-cm name: tls-certs - configMap: name: argocd-gpg-keys-cm name: gpg-keys - emptyDir: {} name: gpg-keyring - emptyDir: {} name: argo-tools
Тут создаём emptyDir
volume с именем argo-tools, запускаем initContainer
с именем argo-tools, которому монтируем volume argo-tools в каталог /root/.local/share/helm/plugins/
, устанавливаем git
, curl
и bash
, и вызываем helm plugin install https://github.com/futuresimple/helm-secrets
.
Этот же volume argo-tools монтируется к поду argocd-repo-server в каталог /home/argocd/.local/share/helm/plugins/
– и helm
в контейнере argocd-repo-server плагин видит, и может использовать.
Но тут возникает проблема – как вызывать helm secrets install
? Ведь ArgoCD по дефолту вызывает исполняемый файл /usr/local/bin/helm
, и передать какие-то опции ему нельзя.
Поэтому пришлось всё-таки пилить костыль в виде кастомного образа, в который включаем helm-secrets
, sops
, и пишем wrapper-скрипт для вызова helm
.
Сборка образа ArgoCD с плагином helm-secrets
Примеры решения нагуглились тут – How to Handle Kubernetes Secrets with ArgoCD and Sops.
Сначала – напишем наш wrapper-скрипт.
Задача скрипта – принимать вызовы к /usr/local/bin/helm
с командами template
, install
, upgrade
, lint
и diff
, которые понимает плагин helm-secrets
, и передавать их в вызов helm secrets + все аргументы.
После выполнения helm secrets @arguments
– выводится output выполнения helm secrets
, из которого вырезается сообщение “removed ‘secrets.yaml.dec‘”:
#! /bin/sh # helm secrets only supports a few helm commands if [ $1 = "template" ] || [ $1 = "install" ] || [ $1 = "upgrade" ] || [ $1 = "lint" ] || [ $1 = "diff" ] then # Helm secrets add some useless outputs to every commands including template, namely # 'remove: <secret-path>.dec' for every decoded secrets. # As argocd use helm template output to compute the resources to apply, these outputs # will cause a parsing error from argocd, so we need to remove them. # We cannot use exec here as we need to pipe the output so we call helm in a subprocess and # handle the return code ourselves. out=$(helm.bin secrets $@) code=$? if [ $code -eq 0 ]; then # printf insted of echo here because we really don't want any backslash character processing printf '%s\n' "$out" | sed -E "/^removed '.+\.dec'$/d" exit 0 else exit $code fi else # helm.bin is the original helm binary exec helm.bin $@ fi
Далее, надо собрать Docker-образ, в котором будут установлены helm-scerets
, sops
, и в котором вызов /usr/local/bin/helm
будет заменён вызовом нашего скрипта.
Находим последнюю версию SOPS – https://github.com/mozilla/sops/releases/ и последнюю версию Helm-secrets – https://github.com/zendesk/helm-secrets/releases.
Пишем Dockerfile:
FROM argoproj/argocd:v1.7.9 ARG SOPS_VERSION="v3.6.1" ARG HELM_SECRETS_VERSION="2.0.2" USER root COPY helm-wrapper.sh /usr/local/bin/ RUN apt-get update --allow-insecure-repositories --allow-unauthenticated && \ apt-get install -y \ curl \ gpg && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ curl -o /usr/local/bin/sops -L https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux && \ chmod +x /usr/local/bin/sops && \ cd /usr/local/bin && \ mv helm helm.bin && \ mv helm2 helm2.bin && \ mv helm-wrapper.sh helm && \ ln helm helm2 && \ chmod +x helm helm2 # helm secrets plugin should be installed as user argocd or it won't be found USER argocd RUN /usr/local/bin/helm.bin plugin install https://github.com/zendesk/helm-secrets --version ${HELM_SECRETS_VERSION} ENV HELM_PLUGINS="/home/argocd/.local/share/helm/plugins/"
Собираем образ – репозиторий в Docker Hub публичный, можно использовать этот образ.
Тегаем версией ArgoCD, которая использовалась, и свою версию сборки, тут это 1:
[simterm]
$ docker build -t setevoy/argocd-helm-secrets:v1.7.9-1 . $ docker push setevoy/argocd-helm-secrets:v1.7.9-1
[/simterm]
Далее, нам надо обновить install.yaml
, из которого деплоился ArgoCD (Helm-чарт пока не использовал).
SOPS и AWS KMS – аутентификация
“Какая боль, какая боль!” (с)
В нашем случае для шифрования данных используется AWS Key Management Service, следовательно SOPS в контейнере который мы запустим из образа setevoy/argocd-helm-secrets:v1.7.9-1
должен иметь к нему доступ.
Для SOPS требуются файлы ~/.aws/credentials
и ~/.aws/config
, который создадим из Kubernetes Secrets.
Наверно, можно было бы попробовать ServiceAccount с IAM ролью, которая давала бы доступ к ключу – но пока сделаю так.
AWS IAM User
Создадим отдельно пользователя для доступа к ключу – переходим в AWS IAM, задаём ему Programmatic access:
Далее, создаём политику на ReadOnly только к ключу, который используется SOPS:
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "kms:ListKeys", "kms:ListAliases", "kms:DescribeKey", "kms:ListKeyPolicies", "kms:GetKeyPolicy", "kms:GetKeyRotationStatus", "iam:ListUsers", "iam:ListRoles" ], "Resource": "arn:aws:kms:us-east-2:534***385:key/f73daf0d-***-440ca3b6547b", "Effect": "Allow" } ] }
Сохраняем, и подключаем её:
Сохраняем пользователя, переходим в AWS KMS, и добавляем Key User:
Настраиваем новый локальный AWS-профиль:
[simterm]
$ aws configure --profile argocd-kms AWS Access Key ID [None]: AKI***Q4F AWS Secret Access Key [None]: S7c***6ya Default region name [None]: us-east-2 Default output format [None]:
[/simterm]
Проверяем доступ к ключу:
[simterm]
$ aws --profile argocd-kms kms describe-key --key-id f73daf0d-***-440ca3b6547b { "KeyMetadata": { "AWSAccountId": "534***385", "KeyId": "f73daf0d-***-440ca3b6547b", "Arn": "arn:aws:kms:us-east-2:534***385:key/f73daf0d-***-440ca3b6547b", ...
[/simterm]
Этот профиль будем использовать при шифровании секрета, и его же надо добавить в под argocd-repo-server.
AWS credentials
и config
Cоздаём новый секрет, в котором описываем файлы ~/.aws/credentials
и ~/.aws/config
, которые потом замапим в под argocd-repo-server:
--- apiVersion: v1 kind: Secret metadata: name: argocd-aws-credentials namespace: dev-1-devops-argocd-ns type: Opaque stringData: credentials: | [argocd-kms] aws_access_key_id = AKI***Q4F aws_secret_access_key = S7c***6ya config: | [profile argocd-kms] region = us-east-2
Добавляем его в .gitignore
:
[simterm]
$ cat .gitignore argocd-aws-credentials.yaml
[/simterm]
В будущем, когда будет делаться автоматизация для развёртывания ArgoCD, можно будет его создавать из Jenkins Secrets.
Создаём секрет:
[simterm]
$ kubectl apply -f argocd-aws-credentials.yaml secret/argocd-aws-credentials created
[/simterm]
Обновляем Deployment
argocd-repo-server – меняем используемый образ, добавляем новый volume из нашего секрета, и монтируем его каталогом /home/argocd/.aws
в контейнере с Argo:
--- apiVersion: apps/v1 kind: Deployment metadata: labels: app.kubernetes.io/component: repo-server app.kubernetes.io/name: argocd-repo-server app.kubernetes.io/part-of: argocd name: argocd-repo-server spec: selector: matchLabels: app.kubernetes.io/name: argocd-repo-server template: metadata: labels: app.kubernetes.io/name: argocd-repo-server spec: automountServiceAccountToken: false containers: - command: - uid_entrypoint.sh - argocd-repo-server - --redis - argocd-redis:6379 # image: argoproj/argocd:v1.7.9 image: setevoy/argocd-helm-secrets:v1.7.9-1 imagePullPolicy: Always name: argocd-repo-server ports: - containerPort: 8081 - containerPort: 8084 readinessProbe: initialDelaySeconds: 5 periodSeconds: 10 tcpSocket: port: 8081 volumeMounts: - mountPath: /app/config/ssh name: ssh-known-hosts - mountPath: /app/config/tls name: tls-certs - mountPath: /app/config/gpg/source name: gpg-keys - mountPath: /app/config/gpg/keys name: gpg-keyring - mountPath: /home/argocd/.aws name: argocd-aws-credentials volumes: - configMap: name: argocd-ssh-known-hosts-cm name: ssh-known-hosts - configMap: name: argocd-tls-certs-cm name: tls-certs - configMap: name: argocd-gpg-keys-cm name: gpg-keys - emptyDir: {} name: gpg-keyring - name: argocd-aws-credentials secret: secretName: argocd-aws-credentials
Обновляем ArgoCD:
[simterm]
$ kubectl -n dev-1-devops-argocd-ns apply -f install.yaml
[/simterm]
Проверяем поды:
[simterm]
$ kubectl -n dev-1-devops-argocd-ns get pod NAME READY STATUS RESTARTS AGE ... argocd-repo-server-64f4bbf4b7-jcs6x 1/1 Terminating 0 19h argocd-repo-server-7c64775679-9jjq2 1/1 Running 0 12s
[/simterm]
Проверяем файлы:
[simterm]
$ kubectl -n dev-1-devops-argocd-ns exec -ti argocd-repo-server-7c64775679-9jjq2 -- cat /home/argocd/.aws/credentials [argocd-kms] aws_access_key_id = AKI***Q4F aws_secret_access_key = S7c***6ya
[/simterm]
И пробуем использовать helm-secrets
.
Добавление secrets.yaml
В репозитории с чартом создаём файл secrets.yaml
:
somePassword: secretValue
Создаём .sops.yaml
с указанием KMS-ключа для шифрования и AWS-профиля:
--- creation_rules: - kms: 'arn:aws:kms:us-east-2:534****385:key/f73daf0d-***-440ca3b6547b' aws_profile: argocd-kms
Шифруем файл:
[simterm]
$ helm secrets enc secrets.yaml Encrypting secrets.yaml Encrypted secrets.yaml
[/simterm]
В тестовый чарт добавим использование секрета, например создание переменной TEST_SECRET_PASSWORD
– обновляем файл templates/deployment.yaml
:
... containers: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} env: - name: TEST_SECRET_PASSWORD value: {{ .Values.somePassword }} ...
Пушим изменения в репозиторий:
[simterm]
$ git add secrets.yaml templates/deployment.yaml $ git commit -m "test secret added" && git push
[/simterm]
Переходим в настройки приложения – App Details > Parameters, жмём Edit и добавляем values.yaml
и secrets.yaml
как Values Files:
ArgoCD теперь видит, что приложение рассинхронизировано с тем, что в репозитории:
Синхронизируем:
Проверяем новый под:
И напрямую в поде:
[simterm]
$ kubectl -n dev-1-devops-test-helm-chart-ns exec -ti test-helm-chart-5c777f9c9d-wkx6s -- printenv | grep SECRET TEST_SECRET_PASSWORD=secretValue
[/simterm]
Готово – секрет на месте.