ArgoCD: обзор, запуск, настройка SSL, деплой приложения

Автор: | 19/11/2020
 

ArgoCD помогает деплоить приложения в Kubernetes, используя GitOps подход, т.е. когда приложения, конфиги, манифесты и тому подобные данных хранятся в Git-репозитории.

Поддерживает работу с “голыми” манифестами Kubernetes, kustomize, ksonnet, jsonnet и то, чем пользуемся мы – Helm-шаблонами.

ArgoCD запускает свой контроллер в Kubernetes-кластере, и отслеживает изменения в Git-репозиториях, синхронизируя приложения в кластере с их манифестами в репозитории.

Из особых вкусностей – SSO с SAML, что позволит интегрировать ArgoCD с нашей Okta, возможность деплоить на несколько кластеров, поддержка Kubernetes RBAC, шикарный WebUI и наличие CLI, интеграция с вебхуками Github, GitLab, etc, плюс Prometehus-метрики из коробки и прекрасная документация.

Планируем использовать его для деплоя Helm-чартов вместо Jenkins, см. Helm: пошаговое создание чарта и деплоймента из Jenkins.

По GitOps и ArgoCD есть очень толковый доклад от Igor Borodin на XPDays вот тут>>>, правда 2019 – но с наглядными примерами.

И стоит почитать о недостатках GitOps в посте GitOps: The Bad and the Ugly.

Компоненты

ArgoCD состоит из трёх компонентов – API-сервер, Repository Server и Application Controller.

  • API-сервер (pod: argocd-server): отвечает за управление всем приложением, вызов всех операций, управление данными доступа, которые хранятся в виде Kubernetes Secrets, аутентификацию и прочее
  • Repository Server (pod: argocd-repo-server): хранит локальные копии данных из Git-репозиториев проектов и генерирует Kubernetes-манифесты
  • Application Controller (pod: argocd-application-controller): мониторит приложения в Kubernetes, и сравнивает их состояние с тем, которое описано в репозитории, плюс отвечает за вызов PreSync, Sync, PostSync хуков

Установка ArgoCD CLI

В macOS:

[simterm]

$ brew install argocd

[/simterm]

В Linux – из Github:

[simterm]

$ VERSION=$(curl --silent "https://api.github.com/repos/argoproj/argo-cd/releases/latest" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
$ sudo curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/$VERSION/argocd-linux-amd64
$ sudo chmod +x /usr/local/bin/argocd

[/simterm]

Проверяем:

[simterm]

$ argocd version
argocd: v1.7.9+f6dc8c3
  BuildDate: 2020-11-17T23:18:20Z
  GitCommit: f6dc8c389a00d08254f66af78d0cae1fdecf7484
  GitTreeState: clean
  GoVersion: go1.14.12
  Compiler: gc
  Platform: linux/amd64

[/simterm]

Запуск ArgoCD в Kubernetes

Ну и запустим ArgoCD. Тут всё просто – используем готовый манифест, который создаст CRD, ServiceAccount-ы, RBAC-роли и биндинги, ConfigMaps, Secrets, Services и Deployments.

Хотя вообще должен быть готовый Helm-чарт – но пока сделаем так, как описано в Getting Started.

Проще запускать в неймспейсе argocd, который предлагает сам ArgoCD в документации, но мы простых путей не ищем, поэтому – создаём свой namespace:

[simterm]

$ kubectl create namespace dev-1-devops-argocd-ns
namespace/dev-1-devops-argocd-ns created

[/simterm]

Деплоим ресурсы:

[simterm]

$ kubectl apply -n dev-1-devops-argocd-ns -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

[/simterm]

Редактируем Sevrice argcd-server – меняем ему тип на LoadBalancer, что бы получить доступ к WebUI из мира:

[simterm]

$ kubectl -n dev-1-devops-argocd-ns patch svc argocd-server -p '{"spec": {"type": "LoadBalancer"}}'
service/argocd-server patched

[/simterm]

Находим его URL:

[simterm]

$ kubectl -n dev-1-devops-argocd-ns get svc argocd-server
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP                                                              PORT(S)                      AGE
argocd-server   LoadBalancer   172.20.142.44   ada***585.us-east-2.elb.amazonaws.com   80:32397/TCP,443:31693/TCP   4m21s

[/simterm]

Пароль ArgoCD генерируется во время установки, и по дефолту задан равным имени пода – получаем его:

[simterm]

$ kubectl -n dev-1-devops-argocd-ns get pods -l app.kubernetes.io/name=argocd-server -o name | cut -d'/' -f 2
argocd-server-794857c8fb-xqgmv

[/simterm]

Логинимся через CLI, на ошибку сертификата пока внимания не обращаем:

[simterm]

$ argocd login ada***585.us-east-2.elb.amazonaws.com
WARNING: server certificate had error: x509: certificate is valid for localhost, argocd-server, argocd-server.dev-1-devops-argocd-ns, argocd-server.dev-1-devops-argocd-ns.svc, argocd-server.dev-1-devops-argocd-ns.svc.cluster.local, not ada***585.us-east-2.elb.amazonaws.com. Proceed insecurely (y/n)? y
Username: admin
Password: 
'admin' logged in successfully
Context 'ada***585.us-east-2.elb.amazonaws.com' updated

[/simterm]

Меняем пароль:

[simterm]

$ argocd account update-password
*** Enter current password: 
*** Enter new password: 
*** Confirm new password: 
Password updated

[/simterm]

Открываем WebUI, снова игнорируем ошибку сертификата – сейчас всё настроим. Логинимся:

LoadBalancer, SSL и DNS

Окей – всё запустилось, настроим нормальное имя и SSL.

AWS ALB и ELB не поддерживают gRPC, см. AWS Application Load Balancers (ALBs) And Classic ELB (HTTP Mode), поэтому ALB Ingress Controller использовать не получится – используем просто Service с типом LoadBalancer, как уже сделали выше – он создаёт AWS Classic LoadBalancer.

Сертификат получаем в AWS Certificate Manager в том же регионе, где кластер и балансировщик, сохраняем его ARN.

Качаем файл манифеста:

[simterm]

$ wget https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

[/simterm]

Находим Service argocd-server:

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/name: argocd-server
    app.kubernetes.io/part-of: argocd
  name: argocd-server
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  - name: https
    port: 443
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/name: argocd-server

В annotations добавляем service.beta.kubernetes.io/aws-load-balancer-ssl-cert, в spec.type – тип LoadBalancer, и ограничиваем доступ через loadBalancerSourceRanges:

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/name: argocd-server
    app.kubernetes.io/part-of: argocd
  name: argocd-server
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:us-east-1:534***385:certificate/ddaf55b0-***-53d57c5ca706"
spec:
  type: LoadBalancer
  loadBalancerSourceRanges:
  - "31.***.***.117/32"
  - "194.***.***.24/29"
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  - name: https
    port: 443
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/name: argocd-server

Деплоим:

[simterm]

$ kubectl -n dev-1-devops-argocd-ns apply -f install.yaml

[/simterm]

Проверяем ELB SSL:

Создаём DNS:

Открываем URL – и ловим ERR_TOO_MANY_REDIRECTS:

ArgoCD SSL: ERR_TOO_MANY_REDIRECTS

Гуглим, находим https://github.com/argoproj/argo-cd/issues/2953.

Возвращаемся к install.yaml, в Deployment argocd-server добавляем --insecure:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/name: argocd-server
    app.kubernetes.io/part-of: argocd
  name: argocd-server
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: argocd-server
  template:
    metadata:
      labels:
        app.kubernetes.io/name: argocd-server
    spec:
      containers:
      - command:
        - argocd-server
        - --staticassets
        - /shared/app
        - --insecure
...

Снова деплоим, и проверяем:

Окей – тут готово.

ArgoCD: деплой из Github

Ну и не будем далеко уходить от гайда – задеплоим тестовое приложение. Helm-чарт будет в следующей части.

Кликаем New App, указываем имя и Project == default:

В Git указваем URL https://github.com/argoproj/argocd-example-apps.git и путь внутри репозитория в каталогу – guestbook:

В Destination – https://kubernetes.default.svc и default namespace, жмём Create:

Вроде бы создало, но почему в Sync Status – Unknown?

Что-то пошло не так:

[simterm]

$ argocd app get guestbook
Name:               guestbook
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
...

CONDITION        MESSAGE                                                                                                                                                                                                                                                                    LAST TRANSITION
ComparisonError  failed to load initial state of resource PersistentVolumeClaim: persistentvolumeclaims is forbidden: User "system:serviceaccount:dev-1-devops-argocd-ns:argocd-application-controller" cannot list resource "persistentvolumeclaims" in API group "" at the cluster scope  2020-11-19 15:35:51 +0200 EET
ComparisonError  failed to load initial state of resource PersistentVolumeClaim: persistentvolumeclaims is forbidden: User "system:serviceaccount:dev-1-devops-argocd-ns:argocd-application-controller" cannot list resource "persistentvolumeclaims" in API group "" at the cluster scope  2020-11-19 15:35:51 +0200 EET

[/simterm]

Пробуем sync – и тоже не работает:

[simterm]

$ argocd app sync guestbook

Name:               guestbook
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
...
Sync Status:        Unknown
Health Status:      Healthy

Operation:          Sync
Sync Revision:      
Phase:              Error
Start:              2020-11-19 15:37:01 +0200 EET
Finished:           2020-11-19 15:37:01 +0200 EET
Duration:           0s
Message:            ComparisonError: failed to load initial state of resource Pod: pods is forbidden: User "system:serviceaccount:dev-1-devops-argocd-ns:argocd-application-controller" cannot list resource "pods" in API group "" at the cluster scope;ComparisonError: failed to load initial state of resource Pod: pods is forbidden: User "system:serviceaccount:dev-1-devops-argocd-ns:argocd-application-controller" cannot list resource "pods" in API group "" at the cluster scope
FATA[0001] Operation has completed with phase: Error

[/simterm]

ArgocD: ComparisonError failed to load initial state of resource

Собственно, проще искать причину по ошибке “User “system:serviceaccount:dev-1-devops-argocd-ns:argocd-application-controller” cannot list resource “pods” in API group “” at the cluster scope“.

Проверяем ServiceAccount argocd-application-controller (пригодилось Kubernetes: ServiceAccounts, JWT-токены, аутентификация и RBAC-авторизация):

[simterm]

$ kubectl -n dev-1-devops-argocd-ns get serviceaccount argocd-application-controller
NAME                            SECRETS   AGE
argocd-application-controller   1         36m

[/simterm]

Да, наш User system:serviceaccount:dev1-devops-argocd-ns:argocd-application-controller есть.

И этому ServiceAccount мапится ClusterRoleBinding argocd-application-controller:

[simterm]

$ kubectl -n dev-1-devops-argocd-ns get clusterrolebinding argocd-application-controller
NAME                            AGE
argocd-application-controller   24h

[/simterm]

Но прав на выполнение list pods у этого ServiceAccount нет:

[simterm]

$ kubectl auth can-i list pods -n dev-1-devops-argocd-ns --as system:serviceaccount:dev-1-devops-argocd-ns:argocd-application-controller
no

[/simterm]

Хотя в ClusterRole все права есть:

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/component: application-controller
    app.kubernetes.io/name: argocd-application-controller
    app.kubernetes.io/part-of: argocd
  name: argocd-application-controller
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs:
  - '*'
- nonResourceURLs:
  - '*'
  verbs:
  - '*'

Проверяем ClusterRoleBinding ещё раз, теперь с выводом всего через -o yaml:

[simterm]

$ kubectl get clusterrolebinding argocd-application-controller -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
...
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: argocd-application-controller
subjects:
- kind: ServiceAccount
  name: argocd-application-controller
  namespace: argocd

[/simterm]

namespace: argocd – “Ага, вот эти ребята!” (c)

Находим две ClusterRoleBinding в install.yamlargocd-application-controller и argocd-server, меняем неймспейс:

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/component: application-controller
    app.kubernetes.io/name: argocd-application-controller
    app.kubernetes.io/part-of: argocd
  name: argocd-application-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: argocd-application-controller
subjects:
- kind: ServiceAccount
  name: argocd-application-controller
  namespace: dev-1-devops-argocd-ns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/name: argocd-server
    app.kubernetes.io/part-of: argocd
  name: argocd-server
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: argocd-server
subjects:
- kind: ServiceAccount
  name: argocd-server
  namespace: dev-1-devops-argocd-ns

Собственно, вот, почему в начале говорил, что проще делать в дефолтном нейспейсе – если деплоить в кастомный, то придётся тут задавать неймспейсы. Но у нас свои naming convention для неймспейсов, стараемся их соблюдать.

Хотя тут ещё зависит от того, как деплоить сам ArgoCD – на каждый кластер свою копию, или делать центральный инстанс ArgoCD – и с него деплоить на разные кластера. Плюс, если делать через Helm-чарт – там наверняка можно в параметрах указывать.

Передеплоиваем, проверяем:

[simterm]

$ kubectl auth can-i list pods -n dev-1-devops-argocd-ns --as system:serviceaccount:dev-1-devops-argocd-ns:argocd-application-controller
yes

[/simterm]

Пробуем ещё раз sync:

[simterm]

$ argocd app sync guestbook
TIMESTAMP  GROUP        KIND   NAMESPACE                  NAME    STATUS   HEALTH        HOOK  MESSAGE
2020-11-19T15:47:08+02:00            Service     default          guestbook-ui   Running   Synced              service/guestbook-ui unchanged
2020-11-19T15:47:08+02:00   apps  Deployment     default          guestbook-ui   Running   Synced              deployment.apps/guestbook-ui unchanged

Name:               guestbook
Project:            default
Server:             https://kubernetes.default.svc
...
Sync Status:        Synced to HEAD (6bed858)
Health Status:      Healthy

Operation:          Sync
Sync Revision:      6bed858de32a0e876ec49dad1a2e3c5840d3fb07
Phase:              Succeeded
Start:              2020-11-19 15:47:06 +0200 EET
Finished:           2020-11-19 15:47:08 +0200 EET
Duration:           2s
Message:            successfully synced (all tasks run)

GROUP  KIND        NAMESPACE  NAME          STATUS  HEALTH   HOOK  MESSAGE
       Service     default    guestbook-ui  Synced  Healthy        service/guestbook-ui unchanged
apps   Deployment  default    guestbook-ui  Synced  Healthy        deployment.apps/guestbook-ui unchanged

[/simterm]

Работает:

И логи пода:

Следующий шаг – разобраться с деплоем Helm-чартов, и как прикрутить работу с Helm Secrets – там придётся или билдить кастомный Docker-образ с ArgoCD, в котором будет установлен Helm-плагин secrets – или делать через Kubernetes InitContainers – варианты есть. Посмотрим, как проще.