Создать новое приложение, кластер или репозиторий в 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]
Проверяем приложения:
Готово.






