|
| 1 | +--- |
| 2 | +title: "Registering a Spoke Cluster to EKS ArgoCD Capability" |
| 3 | +date: 2026-01-06T14:39:01+08:00 |
| 4 | +draft: false |
| 5 | +description: "Learn how to register spoke clusters to Amazon EKS ArgoCD Capability for multi-cluster GitOps deployments" |
| 6 | +tags: ["kubernetes", "argocd", "eks", "gitops", "aws"] |
| 7 | +categories: ["DevOps"] |
| 8 | +author: "Shawn Zhang" |
| 9 | +showToc: true |
| 10 | +TocOpen: false |
| 11 | +hidemeta: false |
| 12 | +comments: false |
| 13 | +disableHLJS: false |
| 14 | +disableShare: false |
| 15 | +searchHidden: false |
| 16 | +cover: |
| 17 | + image: "https://d2908q01vomqb2.cloudfront.net/fe2ef495a1152561572949784c16bf23abb28057/2025/12/18/ArgoCD-EKS-Multi-Cluster.drawio.png" |
| 18 | + alt: "Hub-and-Spoke Architecture for EKS ArgoCD Capability" |
| 19 | + caption: "Source: AWS Containers Blog" |
| 20 | + relative: false |
| 21 | + hidden: false |
| 22 | +--- |
| 23 | + |
| 24 | +## Overview |
| 25 | + |
| 26 | +[Amazon EKS Capabilities](https://docs.aws.amazon.com/eks/latest/userguide/capabilities.html) is a set of fully managed cluster features that accelerate developer velocity and offload the complexity of building and scaling with Kubernetes. Among these capabilities, the **ArgoCD Capability** provides a fully managed GitOps continuous deployment solution—eliminating the operational overhead of installing, maintaining, and scaling Argo CD controllers on your clusters. |
| 27 | + |
| 28 | +[Argo CD](https://argo-cd.readthedocs.io/) is a declarative, GitOps continuous delivery tool for Kubernetes. It follows the GitOps pattern where Git repositories serve as the single source of truth for defining the desired application state. Argo CD continuously monitors these repositories and automatically syncs the cluster state to match the desired configuration. |
| 29 | + |
| 30 | +With the EKS ArgoCD Capability, AWS handles scaling, upgrades, and inter-cluster communications, while providing native integrations with services like Amazon ECR, AWS Secrets Manager, and AWS CodeConnections. |
| 31 | + |
| 32 | +### Hub-and-Spoke Architecture |
| 33 | + |
| 34 | +A common deployment pattern is the **hub-and-spoke** topology: the ArgoCD Capability runs on a dedicated central EKS cluster (the hub) that serves as the control plane for GitOps operations, managing deployments to multiple workload clusters (spokes). This provides a single pane of glass to orchestrate deployments across clusters—whether they're in different regions, accounts, or have private API endpoints. |
| 35 | + |
| 36 | + |
| 37 | +*Figure: Hub-and-spoke topology for EKS ArgoCD Capability (Source: [AWS Containers Blog](https://aws.amazon.com/blogs/containers/deep-dive-streamlining-gitops-with-amazon-eks-capability-for-argo-cd/))* |
| 38 | + |
| 39 | +**This post focuses on registering a spoke cluster to an existing hub cluster with ArgoCD Capability enabled.** |
| 40 | + |
| 41 | +## Prerequisites |
| 42 | + |
| 43 | +- Hub cluster with ArgoCD Capability enabled |
| 44 | +- ArgoCD Capability IAM role ARN (e.g., `ArgoCDCapabilityRole`) |
| 45 | +- kubectl configured for hub cluster |
| 46 | + |
| 47 | +## Step 1: Grant ArgoCD Access to Spoke Cluster |
| 48 | + |
| 49 | +Create an access entry and associate cluster admin policy on the spoke cluster: |
| 50 | + |
| 51 | +```bash |
| 52 | +# Create access entry |
| 53 | +aws eks create-access-entry \ |
| 54 | + --cluster-name <spoke-cluster> \ |
| 55 | + --region <region> \ |
| 56 | + --principal-arn arn:aws:iam::<account-id>:role/ArgoCDCapabilityRole |
| 57 | + |
| 58 | +# Associate cluster admin policy |
| 59 | +aws eks associate-access-policy \ |
| 60 | + --cluster-name <spoke-cluster> \ |
| 61 | + --region <region> \ |
| 62 | + --principal-arn arn:aws:iam::<account-id>:role/ArgoCDCapabilityRole \ |
| 63 | + --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy \ |
| 64 | + --access-scope type=cluster |
| 65 | +``` |
| 66 | + |
| 67 | +## Step 2: Create ArgoCD Project |
| 68 | + |
| 69 | +Create a dedicated project for spoke workloads (skip if already exists): |
| 70 | + |
| 71 | +```yaml |
| 72 | +apiVersion: argoproj.io/v1alpha1 |
| 73 | +kind: AppProject |
| 74 | +metadata: |
| 75 | + name: spoke-workloads |
| 76 | + namespace: argocd |
| 77 | +spec: |
| 78 | + destinations: |
| 79 | + - namespace: '*' |
| 80 | + name: <spoke-cluster> # Add each spoke cluster |
| 81 | + sourceRepos: |
| 82 | + - '*' |
| 83 | + clusterResourceWhitelist: |
| 84 | + - group: '*' |
| 85 | + kind: '*' |
| 86 | + sourceNamespaces: |
| 87 | + - argocd # Required for apps in argocd namespace |
| 88 | +``` |
| 89 | +
|
| 90 | +To add more clusters to an existing project: |
| 91 | +
|
| 92 | +```bash |
| 93 | +kubectl patch appproject spoke-workloads -n argocd --type=json \ |
| 94 | + -p='[{"op": "add", "path": "/spec/destinations/-", "value": {"namespace": "*", "name": "<new-spoke-cluster>"}}]' |
| 95 | +``` |
| 96 | +
|
| 97 | +## Step 3: Register Cluster via Secret |
| 98 | +
|
| 99 | +Create a Kubernetes secret to register the spoke cluster: |
| 100 | +
|
| 101 | +```yaml |
| 102 | +apiVersion: v1 |
| 103 | +kind: Secret |
| 104 | +metadata: |
| 105 | + name: <spoke-cluster> |
| 106 | + namespace: argocd |
| 107 | + labels: |
| 108 | + argocd.argoproj.io/secret-type: cluster |
| 109 | + environment: dev # Optional: for ApplicationSet selectors |
| 110 | + annotations: |
| 111 | + region: <region> # Optional: metadata |
| 112 | +stringData: |
| 113 | + name: <spoke-cluster> |
| 114 | + server: arn:aws:eks:<region>:<account-id>:cluster/<spoke-cluster> |
| 115 | + project: spoke-workloads |
| 116 | +``` |
| 117 | +
|
| 118 | +> **Important**: The `server` field must be the EKS cluster ARN, not the Kubernetes API URL or IAM role ARN. |
| 119 | + |
| 120 | +## Step 4: Deploy Application |
| 121 | + |
| 122 | +Create an ArgoCD Application targeting the spoke cluster: |
| 123 | + |
| 124 | +```yaml |
| 125 | +apiVersion: argoproj.io/v1alpha1 |
| 126 | +kind: Application |
| 127 | +metadata: |
| 128 | + name: my-app |
| 129 | + namespace: argocd |
| 130 | +spec: |
| 131 | + project: spoke-workloads |
| 132 | + source: |
| 133 | + repoURL: https://github.com/org/repo |
| 134 | + targetRevision: HEAD |
| 135 | + path: . |
| 136 | + destination: |
| 137 | + name: <spoke-cluster> # Matches the secret name |
| 138 | + namespace: default |
| 139 | + syncPolicy: |
| 140 | + automated: |
| 141 | + prune: true |
| 142 | + selfHeal: true |
| 143 | + syncOptions: |
| 144 | + - CreateNamespace=true |
| 145 | +``` |
| 146 | + |
| 147 | +## Quick Reference |
| 148 | + |
| 149 | +| Field | Value | Note | |
| 150 | +|-------|-------|------| |
| 151 | +| `server` in Secret | EKS cluster ARN | `arn:aws:eks:<region>:<account>:cluster/<name>` | |
| 152 | +| `destination.name` | Cluster secret name | Not the server URL | |
| 153 | +| `sourceNamespaces` | `argocd` | Required in AppProject | |
| 154 | + |
| 155 | +## Troubleshooting |
| 156 | + |
| 157 | +### Application stuck in Unknown status |
| 158 | + |
| 159 | +Check if the project allows the application: |
| 160 | + |
| 161 | +```bash |
| 162 | +kubectl get application <app-name> -n argocd -o jsonpath='{.status.conditions[*].message}' |
| 163 | +``` |
| 164 | + |
| 165 | +If you see "not permitted to use project", ensure `sourceNamespaces: [argocd]` is set in the AppProject. |
| 166 | + |
| 167 | +### Cluster not reachable |
| 168 | + |
| 169 | +Verify access entry exists: |
| 170 | + |
| 171 | +```bash |
| 172 | +aws eks list-access-entries --cluster-name <spoke-cluster> --region <region> |
| 173 | +``` |
| 174 | + |
| 175 | +Verify access policy is associated: |
| 176 | + |
| 177 | +```bash |
| 178 | +aws eks list-associated-access-policies \ |
| 179 | + --cluster-name <spoke-cluster> \ |
| 180 | + --region <region> \ |
| 181 | + --principal-arn arn:aws:iam::<account-id>:role/ArgoCDCapabilityRole |
| 182 | +``` |
| 183 | + |
| 184 | +### Force refresh application |
| 185 | + |
| 186 | +```bash |
| 187 | +kubectl patch application <app-name> -n argocd --type merge \ |
| 188 | + -p '{"metadata":{"annotations":{"argocd.argoproj.io/refresh":"hard"}}}' |
| 189 | +``` |
| 190 | + |
0 commit comments