Создать новое приложение, кластер или репозиторий в ArgoCD можно как используя WebUI, так и описав его в виде Kubernetes-манифеста, который потом можно передать kubectl
для создания ресурса.
Например, приложения являются CustomResources и описаны в Kubernets CRD applications.argoproj.io
:
[simterm]
$ kubectl get crd applications.argoproj.io NAME CREATED AT applications.argoproj.io 2020-11-27T15:55:29Z
[/simterm]
Которые потом доступны в неймспейсе ArgoCD в виде обычных Kubernetes-ресурсов:
[simterm]
$ kubectl -n dev-1-18-devops-argocd-ns get applications NAME SYNC STATUS HEALTH STATUS backend-app OutOfSync Missing dev-1-18-web-payment-service-ns Synced Healthy web-fe-github-actions Synced Healthy
[/simterm]
Удобен этот подход тем, что при создании нового инстанса ArgoCD не надо будет создавать все приложения вручную. Например, мы при обновлении версии Kubernetes создаём новый кластер, в котором в Jenkins с помощью Ansible и Helm устанавливаются все контроллеры, в т.ч. ArgoCD. В эту же автоматизацию можно добавить и создание всех наших Projects и Applications, которые будут описываться девелоперами в репозитории.
Итак, наша задача настроить автоматическое создание:
- Projects с ролями доступов и разрешениями на неймспейсы
- Applications – для Бекенд и Веб проектов
- Repositories – настроить аутентификацию в Github
Сначала посмотрим, как создавать Porjects и Applications из “голых” манифестов, а потом прикрутим это всё в Ansible и создадим Jenkins-джобу.
Содержание
Projects
Документация – Projects.
Начнём с создания проекта. У нас их будет два, Backend и Web, для каждого надо будет ограничить используемые Namespace и создать роль с доступом к приложениям в этом проекте.
Создадим проект для бекенд-команды:
apiVersion: argoproj.io/v1alpha1 kind: AppProject metadata: name: backend namespace: dev-1-18-devops-argocd-ns finalizers: - resources-finalizer.argocd.argoproj.io spec: description: "Backend project" sourceRepos: - '*' destinations: - namespace: 'dev-1-18-backend-*' server: https://kubernetes.default.svc clusterResourceWhitelist: - group: '' kind: Namespace namespaceResourceWhitelist: - group: "*" kind: "*" roles: - name: "backend-app-admin" description: "Backend team's deployment role" policies: - p, proj:backend:backend-app-admin, applications, *, backend/*, allow groups: - "Backend" orphanedResources: warn: true
Тут:
sourceRepos
: разрешаем деплоить в этом проекте из любых репозиториевdestinations
: в какой кластер и неймспейсы можно будет деплоить приложения в этом проекте, в примере выше используем маску dev-1-18-backend-*, что бы разрешить деплой Бекенд-команды в любые неймспейсы, начинающиеся с dev-1-18-backend-*, а для Веб-команды соответственно будет dev-1-18-web-*clusterResourceWhitelist
: на уровне кластера разрешаем создание только NamespacesnamespaceResourceWhitelist
: на уровне Namespace разрешаем создание любых ресурсовroles
: добавляем роль backend-app-admin с полными правами на приложения в этом проекте, см. ArgoCD: пользователи, доступы и RBAC и ArgoCD: интеграция с Okta и группы пользователейorphanedResources
: включаем вывод предупреждений о “заброшенных” ресурсах, см. Orphaned Resources Monitoring
Деплоим:
[simterm]
$ kubectl apply -f project.yaml appproject.argoproj.io/backend created
[/simterm]
Проверяем:
[simterm]
$ argocd proj get backend Name: backend Description: Backend project Destinations: <none> Repositories: * Whitelisted Cluster Resources: /Namespace Blacklisted Namespaced Resources: <none> Signature keys: <none> Orphaned Resources: enabled (warn=true)
[/simterm]
Applications
Документация – Applications.
Далее, добавим описание тестового приложения, которое будет создаваться в проекте Backend:
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: backend-app namespace: dev-1-18-devops-argocd-ns finalizers: - resources-finalizer.argocd.argoproj.io spec: project: "backend" source: repoURL: https://github.com/projectname/devops-kubernetes.git targetRevision: DVPS-967-ArgoCD-declarative-bootstrap path: tests/test-svc helm: parameters: - name: serviceType value: LoadBalancer destination: server: https://kubernetes.default.svc namespace: dev-1-18-backend-argocdapp-test-ns syncPolicy: automated: prune: false selfHeal: false allowEmpty: false syncOptions: - Validate=true - CreateNamespace=true - PrunePropagationPolicy=foreground - PruneLast=true retry: limit: 5 backoff: duration: 5s factor: 2 maxDuration: 3m
Тут:
finalizers
: указываем на удаление всех ресурсов в Kubernetes при удалении приложения из ArgoCD (cascade deletion, см. App Deletion)project
: проект, в котором будет создано приложение и которые описывает применяемые к приложению ограничения (неймспейсы, ресурсы и т.д.)source:
задаём репозиторий и бранч- в
helm.parameters
– передаём значения для Helm-чарта (helm --set
)
- в
destination
: кластер и неймспейс для деплоя приложения. При этом неймспейс должен быть задан в разрешённых в Project, которому это приложение принадлежитsyncPolicy
: тип синхронизации, см. Automated Sync Policy и Sync Options
Кроме того, ArgoCD поддерживает так называемые App of Apps, когда одно приложение создаёт другое, а то в свою очередь следующее и так далее. Мы использовать (пока) не будем, но механизм интересный, см. Cluster Bootstrapping.
Деплоим наше приложение:
[simterm]
$ kubectl apply -f application.yaml application.argoproj.io/backend-app created
[/simterm]
Проверяем:
[simterm]
$ kubectl -n dev-1-18-devops-argocd-ns get application backend-app NAME SYNC STATUS HEALTH STATUS backend-app Unknown Healthy
[/simterm]
Проверяем статус:
[simterm]
$ argocd app get backend-app Name: backend-app Project: backend Server: https://kubernetes.default.svc Namespace: dev-1-18-backend-argocdapp-test-ns URL: https://dev-1-18.argocd.example.com/applications/backend-app Repo: https://github.com/projectname/devops-kubernetes.git Target: DVPS-967-ArgoCD-declarative-bootstrap Path: tests/test-svc SyncWindow: Sync Allowed Sync Policy: Automated Sync Status: Unknown Health Status: Healthy CONDITION MESSAGE LAST TRANSITION ComparisonError rpc error: code = Unknown desc = authentication required 2021-05-18 09:11:13 +0300 EEST OrphanedResourceWarning Application has 1 orphaned resources 2021-05-18 09:11:13 +0300 EEST
В ошибках у нас сейчас “ComparisonError = authentication required” – ArgoCD не смог подключиться к репозиторию, так как он приватный.
Переходим к настройкам репозиториев и аутентификации.
Repositories
Документация – Repositories.
Репозитории, внезапно, добавляются в argocd-cm
ConfigMap, а не в виде отдельных ресурсов, как Applications или Projects.
Как по мне – не слишком удобное решение, так как если девелопер захочет добавить новый репозиторий – то ему придётся давать права на редактирование системного ConfigMap.
Кроме того, для аутентификации на Git-сервере используются секреты, которые хранятся в неймспейсе ArgoCD, значит – нужен будет доступ и туда.
Однако, у ArgoCD есть механизм, позволяющий выполнять аутентификацию на сервере используя единый секрет, см. Repository Credentials.
Тут идея заключается в том, что мы можем задать “маску”, по которой будет проверяться репозиторий, например, можно добавить репозиторий https://github.com/projectname, к нему подключить данные доступа. Затем, девелопер при создании Application указывает свой репозиторий в виде https://github.com/orgname/reponame, и тогда ArgoCD используя маску github.com/projectname выполнит аутентификацию в Github для доступа к репозиторию github.com/projectname/reponame.
Таким образом, мы можем задать единую “точку аутентификации”, и все девелоперы в их репозиториях будут использовать её, так как все репозитории проекта принадлежат организации orgname.
Repository Secret
Используем HTTPS и Github Access token.
Идём в Github профиль, создаём новый токен (правильнее будет завести отдельного пользователя для ArgoCD, и токен выдать ему):
Даём права на репозитории:
Енкодим токен в base64 в терминале или используя https://www.base64encode.org:
[simterm]
$ echo -n ghp_GE***Vx911 | base64 Z2h***xMQo=
[/simterm]
И имя пользователя:
[simterm]
$ echo -n username | base64 c2V***eTI=
[/simterm]
Описываем Kubernetes Secret, создаём его в неймспейсе ArgoCD:
apiVersion: v1 kind: Secret metadata: name: github-access namespace: dev-1-18-devops-argocd-ns data: username: c2V***eTI= password: Z2h***xMQ==
Применяем:
[simterm]
$ kubectl apply -f secret.yaml secret/github-access created
[/simterm]
Добавляем его использование в argocd-cm
ConfigMap:
... repository.credentials: | - url: https://github.com/orgname passwordSecret: name: github-access key: password usernameSecret: name: github-access key: username ...
Проверяем:
[simterm]
$ argocd repocreds list URL PATTERN USERNAME SSH_CREDS TLS_CREDS https://github.com/projectname username false false
И пробуем синхронизировать приложение:
[simterm]
$ argocd app sync backend-app ... Message: successfully synced (all tasks run) GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE Service dev-1-18-backend-argocdapp-test-ns test-svc Synced Progressing service/test-svc created
[/simterm]
Jenkins, Ansible и ArgoCD
Теперь можно подумать об автоматизации всего этого дела.
У нас ArgoCD устанавливается из Helm-чарта в Ansible-роли с помощью community.kubernetes
, по аналогии с Ansible: модуль community.kubernetes и установка Helm-чарта с ExternalDNS.
Что нам надо добавить:
- в Ansible-роль добавим создание секрета для Github
- в настройках ArgoCD сервера добавим создание
repository.credentials
- отдельно создадим каталоги с Projects и Applications, а в Ansible-роли добавим вызов
community.kubernetes.k8s
, которому будем скармливать манифесты приложений из этого каталога. Таким образом проекты и приложения будут создаваться автоматически при развёртывании кластера и установке нового инстанса ArgoCD, плюс девелоперы смогут сами добавлять манифесты с их Applications
Ansible Kubernetes Secret
В файл переменных, в нашем случае общий group_vars/all.yaml
, добавляем две переменные, которые шифруем с ansible-vault
. Не забываем, что перед тем, как шифровать – строки надо перевести в base64.
Добавляем:
... argocd_github_access_username: !vault | $ANSIBLE_VAULT;1.1;AES256 63623436326661333236383064636431333532303436323735363063333264306535313934373464 ... 3132663634633764360a666162616233663034366536333765666364643363336130323137613333 3636 argocd_github_access_password: !vault | $ANSIBLE_VAULT;1.1;AES256 61393931663234653839326232383435653562333435353435333962363361643634626664643062 ... 6239623265306462343031653834353562613264336230613466 ...
В файл роли, в данном случае roles/argocd/tasks/main.yml
, добавляем создание секрета:
... - name: "Create github-access Secret" community.kubernetes.k8s: definition: kind: Secret apiVersion: v1 metadata: name: "github-access" namespace: "{{ eks_env }}-devops-argocd-2-0-ns" data: username: "{{ argocd_github_access_username }}" password : "{{ argocd_github_access_password }}" ...
В values чарта описываем создание repository.credentials
, см. values.yaml:
... - name: "Deploy ArgoCD chart to the {{ eks_env }}-devops-argocd-2-0-ns namespace" community.kubernetes.helm: kubeconfig: "{{ kube_config_path }}" name: "argocd20" chart_ref: "argo/argo-cd" release_namespace: "{{ eks_env }}-devops-argocd-2-0-ns" create_namespace: true values: ... server: service: type: "LoadBalancer" loadBalancerSourceRanges: ... config: url: "https://{{ eks_env }}.argocd-2-0.example.com" repository.credentials: | - url: "https://github.com/projectname/" passwordSecret: name: github-access key: password usernameSecret: name: github-access key: username ...
Ansible ArgoCD Projects и Applications
Создаём каталоги, в которых будем хранить манифесты для проектов и приложений:
[simterm]
$ mkdir -p roles/argocd/templates/{projects,applications/{backend,web}}
[/simterm]
В каталоге roles/argocd/templates/projects/
создаём два манифеста для двух проектов:
[simterm]
$ vim -p roles/argocd/templates/projects/backend-project.yaml.j2 roles/argocd/templates/projects/web-project.yaml.j2
[/simterm]
Описываем проект Backend:
apiVersion: argoproj.io/v1alpha1 kind: AppProject metadata: name: "backend" namespace: "{{ eks_env }}-devops-argocd-2-0-ns" finalizers: - resources-finalizer.argocd.argoproj.io spec: description: "Backend project" sourceRepos: - '*' destinations: - namespace: "{{ eks_env }}-backend-*" server: "https://kubernetes.default.svc" clusterResourceWhitelist: - group: '' kind: Namespace namespaceResourceWhitelist: - group: "*" kind: "*" roles: - name: "backend-app-admin" description: "Backend team's deployment role" policies: - p, proj:backend:backend-app-admin, applications, *, backend/*, allow groups: - "Backend" orphanedResources: warn: true
Повторяем для Web.
Аналогично делаем для приложения, пока тут одно тестовое.
Создаём файл roles/argocd/templates/applications/backend/backend-test-app.yaml.j2
:
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: "backend-app-1" namespace: "{{ eks_env }}-devops-argocd-2-0-ns" finalizers: - resources-finalizer.argocd.argoproj.io spec: project: "backend" source: repoURL: "https://github.com/projectname/devops-kubernetes.git" targetRevision: "DVPS-967-ArgoCD-declarative-bootstrap" path: "tests/test-svc" helm: parameters: - name: serviceType value: LoadBalancer destination: server: "https://kubernetes.default.svc" namespace: "{{ eks_env }}-backend-argocdapp-test-ns" syncPolicy: automated: prune: true selfHeal: false syncOptions: - Validate=true - CreateNamespace=true - PrunePropagationPolicy=foreground - PruneLast=true retry: limit: 2
И в конце задачи добавляем применение этих манифестов – сначала создаём проекты, потом приложения:
... - name: "Create a Backend project" community.kubernetes.k8s: kubeconfig: "{{ kube_config_path }}" state: present template: roles/argocd/templates/projects/backend-project.yaml.j2 - name: "Create a Web project" community.kubernetes.k8s: kubeconfig: "{{ kube_config_path }}" state: present template: roles/argocd/templates/projects/web-project.yaml.j2 - name: "Create Backend applications" community.kubernetes.k8s: kubeconfig: "{{ kube_config_path }}" state: present template: "{{ item }}" with_fileglob: - roles/argocd/templates/applications/backend/*.yaml.j2 - name: "Create Web applications" community.kubernetes.k8s: kubeconfig: "{{ kube_config_path }}" state: present template: "{{ item }}" with_fileglob: - roles/argocd/templates/applications/web/*.yaml.j2
Для Applications используем with_fileglob
, что бы выбрать все файлы из каталога roles/argocd/templates/applications/
, так как задумывается иметь отдельный файл шаблона для каждого приложения, что бы девелоперам было проще их создавать и обновлять.
Jenkins
См. примеры в Jenkins: миграция RTFM 2.6 – Jenkins Pipeline для Ansible и Helm: пошаговое создание чарта и деплоймента из Jenkins.
Подробно останавливаться не буду, тут всё достаточно просто и для нас стандартно: используем Scripted Pipeline, в которой вызываем функцию provision.ansibleApply()
:
... stage("Applly") { // ansibleApply((playbookFile='1', tags='2', passfile_id='3', limit='4') provision.ansibleApply( "${PLAYBOOK}", "${env.TAGS}", "${PASSFILE_ID}", "${EKS_ENV}") } ...
Сама функция выглядит так:
... def ansibleApply(playbookFile='1', tags='2', passfile_id='3', limit='4') { withCredentials([file(credentialsId: "${passfile_id}", variable: 'passfile')]) { docker.image('projectname/kubectl-aws:4.5').inside('-v /var/run/docker.sock:/var/run/docker.sock --net=host') { sh """ aws sts get-caller-identity ansible-playbook ${playbookFile} --tags ${tags} --vault-password-file ${passfile} --limit ${limit} """ } } }. ...
Тут в Docker запускается контейнер с нашим образ с AWS CLI и Ansible, который запускает Ansible playbook, передаёт ему tag
, и запускает нужную роль.
И в самом плейбуке для каждой роли заданы теги, которые позволяют выполнять только эту определённую роль:
... - role: argocd tags: argocd, create-cluster ...
В результате получается Jenkins Job с такими параметрами:
Запускаем:
Логинимся:
[simterm]
$ argocd login dev-1-18.argocd-2-0.example.com --name [email protected]
[/simterm]
Проверяем приложения:
Готово.