ArgoCD Module¶
Deploys Argo CD as the Stage 2 GitOps controller with Auth0 OIDC, RBAC, Prometheus monitoring, ingress, and an optional root Application.
Architecture¶
flowchart TB
Admin["Administrator browser"]:::external
Developer["Developer"]:::external
GitRepo["Application Git repositories"]:::external
AppsRepo["argocd-apps Git repository"]:::external
Auth0["Auth0 OIDC provider"]:::external
Prometheus["Prometheus"]:::external
subgraph Cluster["Kubernetes cluster"]
Terraform["Stage 2 Terraform<br/>module.argocd"]:::control
Namespace["argocd namespace<br/>prevent_destroy"]:::resource
OIDCSecret["argocd-auth0-secret<br/>OIDC client secret"]:::secret
NotificationsSecret["argocd-notifications-slack-secret<br/>Slack bot token"]:::secret
RBACConfig["argocd-rbac-cm<br/>default role and group policy"]:::resource
HelmRelease["argo-cd Helm release<br/>chart 10.10.5"]:::control
CRDs["Argo CD CRDs<br/>Application, ApplicationSet, AppProject"]:::resource
ListenerSet["ListenerSet plus HTTPRoute<br/>module owned, see httproute.tf"]:::network
TLSSecret["cert-manager TLS Secret"]:::secret
Gateway["Istio Gateway public-istio<br/>namespace istio-ingress"]:::network
Server["argocd-server<br/>API and Web UI"]:::component
Controller["application-controller<br/>desired and live state reconciliation"]:::component
RepoServer["repo-server<br/>clone and manifest generation"]:::component
AppSetController["applicationset-controller<br/>Application generation"]:::component
Notifications["notifications-controller"]:::component
Redis["Redis<br/>shared cache"]:::data
RootApplication["argocd-apps-root Application<br/>optional Terraform resource"]:::resource
ApplicationSets["ApplicationSet resources"]:::resource
Applications["Application resources"]:::resource
KubernetesAPI["Kubernetes API server"]:::control
Workloads["Managed application workloads"]:::resource
ServiceMonitors["ServiceMonitors<br/>controller, server, repo, Redis, ApplicationSet"]:::monitoring
PrometheusRule["PrometheusRule<br/>missing and unsynced applications"]:::monitoring
end
Terraform -->|creates first| Namespace
Terraform -->|stores Auth0 secret| OIDCSecret
Terraform -->|stores Slack token when set| NotificationsSecret
Terraform -->|owns RBAC data| RBACConfig
Terraform -->|installs| HelmRelease
Terraform -->|creates when repo URL is set| RootApplication
Namespace --> HelmRelease
OIDCSecret -->|clientSecret reference| Server
NotificationsSecret -->|slack-token reference| Notifications
RBACConfig -->|authorization policy| Server
HelmRelease --> CRDs
HelmRelease --> Server
HelmRelease --> Controller
HelmRelease --> RepoServer
HelmRelease --> AppSetController
HelmRelease --> Notifications
HelmRelease --> Redis
HelmRelease --> ServiceMonitors
HelmRelease --> PrometheusRule
Terraform -->|owns listener, route, certificate| ListenerSet
Admin -->|HTTPS| Gateway
Gateway -->|matches SNI argocd host| ListenerSet
TLSSecret -->|certificate| ListenerSet
ListenerSet -->|plaintext to port 80, server.insecure| Server
Server -->|authorization request| Auth0
Auth0 -->|OIDC callback| Server
Server -->|API operations| KubernetesAPI
Server -->|cache| Redis
Developer -->|push desired state| GitRepo
Developer -->|push ApplicationSets| AppsRepo
RootApplication -->|watches applicationsets path| AppsRepo
RootApplication -->|syncs| ApplicationSets
ApplicationSets --> AppSetController
AppSetController -->|generates| Applications
Applications --> Controller
Controller -->|requests manifests| RepoServer
RepoServer -->|clone and fetch| GitRepo
Controller -->|read and apply resources| KubernetesAPI
KubernetesAPI --> Workloads
Controller -->|cache| Redis
RepoServer -->|cache| Redis
Server -->|metrics| ServiceMonitors
Controller -->|metrics| ServiceMonitors
RepoServer -->|metrics| ServiceMonitors
AppSetController -->|metrics| ServiceMonitors
Redis -->|metrics| ServiceMonitors
ServiceMonitors -->|scraped by| Prometheus
PrometheusRule -->|evaluated by| Prometheus
classDef external fill:#ecf0f1,color:#2c3e50,stroke:#2c3e50
classDef control fill:#2c3e50,color:#ffffff,stroke:#1a252f
classDef component fill:#0b5394,color:#ffffff,stroke:#073763
classDef resource fill:#38761d,color:#ffffff,stroke:#274e13
classDef network fill:#741b47,color:#ffffff,stroke:#4c1130
classDef secret fill:#783f04,color:#ffffff,stroke:#4f2a03
classDef data fill:#674ea7,color:#ffffff,stroke:#351c75
classDef monitoring fill:#990000,color:#ffffff,stroke:#660000
GitOps Flow¶
sequenceDiagram
participant Developer
participant Git as Application Git repository
participant Controller as Application controller
participant Repo as Repo server
participant API as Kubernetes API
participant Redis
Developer->>Git: Push desired state
Controller->>API: Read Application and live resources
Controller->>Repo: Request manifests for target revision
Repo->>Git: Clone or fetch repository
Git-->>Repo: Commit and source files
Repo->>Redis: Read or update manifest cache
Repo-->>Controller: Rendered manifests
Controller->>Controller: Compare desired and live state
alt drift exists and automated sync is enabled
Controller->>API: Apply desired resources
API-->>Controller: Resource status
Controller->>API: Update Application status
Controller->>Redis: Update reconciliation cache
else no drift or manual sync is required
Controller->>API: Update observed Application status
end
Authentication Flow¶
Argo CD handles OIDC directly. OAuth2 Proxy is not in the Argo CD request path.
This module therefore has no AuthorizationPolicy, unlike every other host on the gateway. The others attach a CUSTOM policy delegating to the oauth2-proxy ext_authz provider. Adding one here would put a second login in front of Argo CD's own OIDC flow and break the callback. The host answering 200 rather than a 302 is how to tell the two arrangements apart.
The argocd CLI speaks gRPC to the same port as the browser UI. The argocd-server Service port is named http, which is what lets Envoy negotiate HTTP/2 to the backend; renaming it to a tcp- prefix would declare an opaque TCP stream and break the CLI while the browser kept working.
sequenceDiagram
participant Browser
participant Gateway as Istio gateway
participant Server as Argo CD server
participant Auth0
participant Secret as argocd-auth0-secret
participant RBAC as argocd-rbac-cm
Browser->>Gateway: HTTPS request
Gateway->>Server: Plaintext request inside cluster
Server-->>Browser: Redirect to Auth0
Browser->>Auth0: Authenticate
Auth0-->>Browser: Authorization response
Browser->>Gateway: OIDC callback
Gateway->>Server: Forward callback
Server->>Secret: Read client secret reference
Server->>Auth0: Exchange code and validate identity
Server->>RBAC: Evaluate groups and default role
Server-->>Browser: Authenticated session
Values Ownership¶
stage2/argocd/templates/argocd-values.tftpl contains only repository-owned overrides; omitted keys inherit the pinned chart defaults.
Keep the template reference and helm_release.argo_cd.version on the same chart tag. For upgrades, review that tag's values.yaml, the Argo CD upgrade guide, and the rendered manifest. Copying chart defaults into the template pins them.
The notification trigger and template catalog is the exception: the chart ships none, so notifications.triggers and notifications.templates are repository-owned and the template carries the full body of on-deployed, on-sync-failed, and on-health-degraded.
Resources Created¶
kubernetes_namespace_v1.argocd: Dedicated namespace guarded byprevent_destroy.kubernetes_secret_v1.argocd_auth0_oidc_secret: Auth0 OIDC client secret.kubernetes_secret_v1.argocd_notifications_secret: Slack bot token read by the notifications controller as$slack-token, created only whenargocd_notifications_slack_tokenis set. Its name,argocd-notifications-slack-secret, is distinct from the chart-owned default Secret so enabling or disabling Slack needs no ownership transfer.kubernetes_config_map_v1.argocd_rbac_cm: Default RBAC role, group policy, scopes, and matching mode.helm_release.argo_cd: Argo CD CRDs, workloads, Services, ServiceMonitors, and PrometheusRule. The chart's Ingress is disabled, seehttproute.tf.kubectl_manifest.listener:ListenerSetcontributing this host's HTTPS listener to the shared Istio Gateway.kubectl_manifest.certificate:Certificateforargocd-server-tls, reissued underletsencrypt-gatewayover DNS-01.kubectl_manifest.route:HTTPRoutesending every path toargocd-server:80with the shared header filters.kubernetes_manifest.argocd_apps_root: Optional root Application for theapplicationsetspath in the central GitOps repository.data.kubernetes_secret_v1.argocd_initial_admin_secret: Initial administrator password output.
Variables¶
| Name | Description | Default |
|---|---|---|
prometheus_namespace |
ServiceMonitor and PrometheusRule namespace | monitoring |
argocd_domain |
Argo CD hostname | argocd.chrislee.local |
istio_gateway_name |
Shared Gateway the ListenerSet attaches to | public |
istio_gateway_namespace |
Namespace of the shared Gateway | istio-ingress |
argocd_config_repositories |
Repository credentials rendered into configs.repositories; empty in this deployment |
[] |
argocd_rbac_policy_default |
Fallback RBAC role for non-admin identities | "" |
argocd_rbac_policy_csv |
RBAC policy CSV | "" |
argocd_apps_repo_url |
GitOps repository for the optional root Application | "" |
auth_oauth2_proxy_host |
Auth0 group claim namespace | auth.chrislee.local |
argocd_auth0_domain |
Auth0 tenant domain | chrislee.auth0.com |
argocd_auth0_client_id |
Auth0 client ID | "" |
argocd_auth0_client_secret |
Auth0 client secret | required, sensitive |
argocd_notifications_slack_token |
Slack bot token for the notifications controller; empty leaves Slack notifications off | "", sensitive |
argocd_notifications_slack_subscriptions |
Default Slack routing; each entry maps triggers to channels with an optional Application label selector |
[] |
Usage¶
1. Configure Auth0 OIDC¶
Create an Auth0 Regular Web Application:
- Allowed Callback URL:
https://argocd.chrislee.local/auth/callback - Allowed Logout URL:
https://argocd.chrislee.local
Configure TF_VAR_argocd_domain, TF_VAR_auth_auth0_domain, TF_VAR_auth_auth0_client_id, and TF_VAR_auth_auth0_client_secret in Bitwarden Secrets Manager.
2. Configure RBAC¶
argocd_rbac_policy_default controls the fallback role and is empty by default, so non-admin SSO and local identities may authenticate but receive no resource access unless argocd_rbac_policy_csv grants it explicitly. The built-in admin remains the unrestricted break-glass account. The CSV maps Auth0 groups and other subjects to Argo CD roles or direct policies.
On an existing cluster an already-exported TF_VAR_argocd_rbac_policy_default overrides this default and keeps the old role:readonly grant. See Stage 2: ArgoCD before applying.
3. Configure the Root Application¶
Set TF_VAR_argocd_apps_repo_url to create argocd-apps-root, which syncs ApplicationSets from the repository's applicationsets directory. Leave it empty to skip the root Application.
4. Configure Repository Credentials¶
Warning
Only [] is supported. The input's Secret selectors cannot be encoded as scalar repository Secret values. Manage credentials as direct Argo CD repository Secrets.
5. Configure Slack Notifications¶
Create the Slack app and obtain its bot token as described in Configure Slack Alerts, then set TF_VAR_argocd_notifications_slack_token to that token. The bot needs the chat:write scope and has to be a member of every target channel. Leave the variable empty to keep notifications off; nothing under notifications is then rendered beyond the resources block.
The token is stored in Terraform state and in prior state versions, so read access to the stage2 workspace state is equivalent to holding the token. To rotate it, revoke the token in the Slack app, update the Bitwarden secret, then apply again.
TF_VAR_argocd_notifications_slack_subscriptions_json_encoded holds the default routing as a JSON array. Each entry needs triggers and channels, and may add a selector that limits the entry to Applications whose labels match. The subscriptions render only when the token is set, so with an empty token they are ignored and the plan shows no change. Their contents are validated on every plan regardless, because Terraform evaluates a variable's validation blocks independently of any other variable.
[
{"triggers": ["on-sync-failed", "on-health-degraded"], "channels": ["homelab-alerts"]},
{"triggers": ["on-deployed"], "channels": ["homelab-deploys"], "selector": "app.kubernetes.io/part-of=homelab"}
]
The available triggers are on-deployed, on-sync-failed, and on-health-degraded. Argo CD takes the channel name without a leading # and the module adds the slack: recipient prefix itself. Individual Applications can still opt in separately through the notifications.argoproj.io/subscribe.<trigger>.slack annotation.
on-deployed deduplicates by Application and sync operation revision. on-sync-failed and on-health-degraded notify when their condition becomes true again after the controller observes it as false, even on the same revision. They suppress repeats while the condition stays true and allow retryable delivery failures to be retried.
6. Get the Initial Administrator Password¶
7. Access the Dashboard¶
Navigate to https://argocd.chrislee.local and authenticate through Auth0.
Helm Chart¶
| Property | Value |
|---|---|
| Repository | https://argoproj.github.io/argo-helm |
| Chart | argo-cd |
| Version | 10.10.5 |
| App Version | v3.5.5 |
Application Example¶
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: git@gitlab.chrislee.local:group/repo.git
targetRevision: HEAD
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
Verification¶
Plan before applying:
After applying, verify the release, rollouts, and Applications:
helm -n argocd list --filter '^argocd$'
kubectl -n argocd rollout status statefulset/argocd-application-controller --timeout=5m
kubectl -n argocd rollout status deployment/argocd-server --timeout=5m
kubectl -n argocd rollout status deployment/argocd-repo-server --timeout=5m
kubectl -n argocd rollout status deployment/argocd-applicationset-controller --timeout=5m
kubectl -n argocd rollout status deployment/argocd-notifications-controller --timeout=5m
kubectl -n argocd rollout status deployment/argocd-redis --timeout=5m
kubectl -n argocd get pods
kubectl -n argocd get applications.argoproj.io
Troubleshooting¶
| Symptom | Check |
|---|---|
| Auth0 redirect loop or rejected callback | Confirm the Auth0 callback URL, inspect argocd-cm, and read argocd-server logs. |
| Login succeeds but applications are hidden | Expected when argocd_rbac_policy_csv grants the user nothing, because argocd_rbac_policy_default is empty. Confirm the CSV first, then the token group claim and the RBAC scopes in argocd-rbac-cm. |
| Repository connection fails | Inspect the repository Secret data keys and argocd-repo-server logs. |
| Application remains OutOfSync | Compare desired and live manifests, then inspect application-controller and repo-server logs before syncing. |
| Metrics or alerts are absent | Verify the ServiceMonitors and PrometheusRule in the monitoring namespace and confirm Prometheus selected them. |