В предыдущем посте 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]
Готово — секрет на месте.



























