{"id":514,"date":"2025-08-09T10:50:23","date_gmt":"2025-08-09T10:50:23","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=514"},"modified":"2025-08-09T10:50:23","modified_gmt":"2025-08-09T10:50:23","slug":"automate-adding-clusters-to-argocd","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=514","title":{"rendered":"Automate Adding Clusters to ArgoCD using GitHub Actions"},"content":{"rendered":"<p>In this blog, we will look into GitHub Actions workflow to automate adding a cluster to ArgoCD.<\/p>\n<p>This workflow removes the repeated process of adding new clusters each time, saving time and reducing the possibility of human error.<\/p>\n<p>We have used an AWS EKS cluster for this setup.<\/p>\n<p>By the end of this blog, you will understand:<\/p>\n<ul>\n<li>Workflow to automate adding clusters to <a href=\"https:\/\/devopscube.com\/argo-cd-ultimate-guide\/\" rel=\"noreferrer\">ArgoCD<\/a>.<\/li>\n<li>Understanding of how the workflow works.<\/li>\n<li>And how to troubleshoot if the cluster is not added correctly.<\/li>\n<\/ul>\n<h2 id=\"runner-configuration\">Runner Configuration<\/h2>\n<p>We used an AWS EC2 instance as a runner for GitHub Actions.<\/p>\n<p>For this workflow to work, the GitHub actions runner should have IAM permissions to get the <a href=\"https:\/\/devopscube.com\/github-actions-runner-aws-eks\/\" rel=\"noreferrer\">EKS cluster<\/a> context.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\u26a0\ufe0f<\/div>\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note: <\/strong><\/b>We tested this in a static VM based Github Actions runner and it has the permission to get cluster context.<\/p>\n<p>If you are using dynamic docker based or pod based runners, you need to make sure it has permissions to get the cluster contexts.<\/p><\/div>\n<\/div>\n<h2 id=\"workflow-diagram\">Workflow Diagram<\/h2>\n<p>The diagram below shows the workflow for automating the addition of clusters to ArgoCD.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/04\/multi-cluster-workflow--1-.png\" class=\"kg-image\" alt=\"Workflow diragram of adding clusters to argocd using github actions\" loading=\"lazy\" width=\"1617\" height=\"1025\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/04\/multi-cluster-workflow--1-.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/04\/multi-cluster-workflow--1-.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/04\/multi-cluster-workflow--1-.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/04\/multi-cluster-workflow--1-.png 1617w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p><strong>Explanation:<\/strong><\/p>\n<ol>\n<li>The workflow starts by deploying an EKS cluster using a  GitHub Actions workflow.<\/li>\n<li>In the next step, the workflow updated the current context to the newly created EKS cluster.<\/li>\n<li>With that context, it creates RBAC objects ( service account, clusterrole, clusterrolebinding) and a token that would be required for ArgoCD to connect to the new cluster.<\/li>\n<li>Then the new clusters Token, CA data, and cluster API endpoints are saved as environment variables in the runner.<\/li>\n<li>Next, the runner changes the cluster context to the cluster where ArgoCD is installed.<\/li>\n<li>Then it creates a secret in the argocd namespace, with the Token, CA data, and cluster API endpoints of the target cluster.<\/li>\n<li>Once the secret is created,  ArgoCD automatically detects the secret because of the <code>argocd.argoproj.io\/secret-type: cluster<\/code> annotation in the secret.<\/li>\n<li>And finally, ArgoCD uses the secret to add the target cluster.<\/li>\n<\/ol>\n<h2 id=\"prerequisites\">Prerequisites<\/h2>\n<p>You should have the following prerequisites for this setup.<\/p>\n<ol>\n<li>At least two <a href=\"https:\/\/devopscube.com\/create-aws-eks-cluster-eksctl\/\" rel=\"noreferrer\">Kubernetes Cluster<\/a> <\/li>\n<li><a href=\"https:\/\/devopscube.com\/setup-argo-cd-using-helm\/\" rel=\"noreferrer\">ArgoCD installed<\/a> on the cluster (assume the cluster is dev)<\/li>\n<li><a href=\"https:\/\/devopscube.com\/github-actions-self-hosted-runner\/\" rel=\"noreferrer\">GitHub Actions runner<\/a> configured<\/li>\n<\/ol>\n<h2 id=\"how-to-automate-adding-clusters-to-argocd\">How to Automate Adding Clusters to ArgoCD?<\/h2>\n<p>First, fork <a href=\"https:\/\/github.com\/techiescamp\/argocd-guide.git?ref=devopscube.com\" rel=\"noreferrer\">this<\/a> GitHub repository and configure your actions on it.<\/p>\n<p>The files we will use for this automation are given in the structure below.<\/p>\n<pre><code>.\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 .github\n\u2502   \u2514\u2500\u2500 workflows\n\u2502        \u2514\u2500\u2500 add-cluster.yaml\n\u2514\u2500\u2500 multi-cluster-configurations\n    \u251c\u2500\u2500 target-cluster-rbac.yaml\n    \u2514\u2500\u2500 traget-cluster-token.yaml<\/code><\/pre>\n<p>The workflow we will use to automate adding clusters is given below.<\/p>\n<pre><code>name: Pipeline to Add Cluster to ArgoCD\non:\n  workflow_dispatch:\n    inputs:\n      environment:\n        description: 'Environment to add (stage or prod)'\n        required: true\n        type: choice\n        options:\n        - stage\n        - prod\n\njobs:\n  add-cluster:\n    runs-on: infra\n    steps:\n    - name: Checkout code\n      uses: actions\/checkout@v3\n\n    - name: Update kubeconfig file of Target Cluster\n      run: |\n        CLUSTER_NAME=\"${{ github.event.inputs.environment }}-cluster\"\n        echo \"CLUSTER_NAME=$CLUSTER_NAME\" &gt;&gt; $GITHUB_ENV\n        aws eks update-kubeconfig --name $CLUSTER_NAME --region us-east-1\n\n    - name: RBAC and Token creation on Target Cluster\n      run: |\n        kubectl apply -f .\/multi-cluster-configurations\/target-cluster-rbac.yaml\n        kubectl apply -f .\/multi-cluster-configurations\/target-cluster-token.yaml       \n\n    - name: Save Token and CA Data to Environment Variables\n      run: |\n          TARGET_TOKEN=$(kubectl get -n kube-system secret\/argocd-manager-token -o jsonpath='{.data.token}' | base64 --decode)\n          TARGET_CA_DATA=$(kubectl get -n kube-system secret\/argocd-manager-token -o jsonpath='{.data.ca\\.crt}')\n          TARGET_CLUSTER_ENDPOINT=$(aws eks describe-cluster --name $CLUSTER_NAME --query \"cluster.endpoint\" --output text | sed 's~https:\/\/~~')\n\n          echo \"TARGET_TOKEN=$TARGET_TOKEN\" &gt;&gt; $GITHUB_ENV\n          echo \"TARGET_CA_DATA=$TARGET_CA_DATA\" &gt;&gt; $GITHUB_ENV\n          echo \"TARGET_CLUSTER_ENDPOINT=$TARGET_CLUSTER_ENDPOINT\" &gt;&gt; $GITHUB_ENV\n\n    - name: Update kubeconfig file to Dev Cluster\n      run: |\n        aws eks update-kubeconfig --name dev-cluster --region us-east-1\n\n    - name: Add Target Cluster to ArgoCD\n      run: |\n\n        cat &lt;&lt;EOF | kubectl apply -n argocd -f -\n        apiVersion: v1\n        kind: Secret\n        metadata:\n          name: ${{ github.event.inputs.environment }}-cluster-secret\n          labels:\n            argocd.argoproj.io\/secret-type: cluster\n        type: Opaque\n        stringData:\n          name: ${{ github.event.inputs.environment }}-cluster\n          server: https:\/\/$TARGET_CLUSTER_ENDPOINT\n          config: |\n            {\n              \"bearerToken\": \"$TARGET_TOKEN\",\n              \"tlsClientConfig\": {\n                \"serverName\": \"$TARGET_CLUSTER_ENDPOINT\",\n                \"caData\": \"$TARGET_CA_DATA\"\n              }\n            }\n        EOF<\/code><\/pre>\n<p><strong>Explanation:<\/strong><\/p>\n<p>Let me explain each block to show you what it does.<\/p>\n<pre><code>name: Pipeline to Add Cluster to ArgoCD\non:\n  workflow_dispatch:\n    inputs:\n      environment:\n        description: 'Environment to add (stage or prod)'\n        required: true\n        type: choice\n        options:\n        - stage\n        - prod<\/code><\/pre>\n<p>This contains the name of the pipeline, and the workflow can be triggered only manually.<\/p>\n<pre><code>jobs:\n  add-cluster:\n    runs-on: infra\n    steps:\n    - name: Checkout code\n      uses: actions\/checkout@v3<\/code><\/pre>\n<p>Here we have specified the runner&#8217;s label and the first step, which clones every file and folder in the repo to the Actions workspace.<\/p>\n<pre><code>- name: Update kubeconfig file of Target Cluster\n  run: |\n    CLUSTER_NAME=\"${{ github.event.inputs.environment }}-cluster\"\n    echo \"CLUSTER_NAME=$CLUSTER_NAME\" &gt;&gt; $GITHUB_ENV\n    aws eks update-kubeconfig --name $CLUSTER_NAME --region $REGION<\/code><\/pre>\n<p>This is the second step, which saves the cluster&#8217;s name as an environment variable and substitutes it in the <code>kubeconfig update<\/code> command to update the cluster&#8217;s context.<\/p>\n<p>The <code>${{ github.event.inputs.environment }}<\/code> gets the values from the option we select in the first block, which is either stage or prod.<\/p>\n<p>Our cluster names are stage-cluster and prod-cluster. You can change the options in the first block according to your cluster names.<\/p>\n<p>For example, if your cluster&#8217;s names are <code>stage-web-app-cluster<\/code> and <code>prod-web-app-cluster<\/code>, then modify the options like:<\/p>\n<pre><code>  options:\n  - stage-web-app\n  - prod-web-app<\/code><\/pre>\n<p>The cluster at the end will be added from the command <code>CLUSTER_NAME=\"${{ github.event.inputs.environment }}-cluster<\/code>.<\/p>\n<p>Also, update the region where your cluster is created.<\/p>\n<pre><code>- name: RBAC and Token creation on Target Cluster\n  run: |\n        kubectl apply -f .\/multi-cluster-configurations\/target-cluster-rbac.yaml\n        kubectl apply -f .\/multi-cluster-configurations\/target-cluster-token.yaml  <\/code><\/pre>\n<p>This step created the RBAC objects (service account, clusterrole, clusterrole binding) and bearer token on the target cluster.<\/p>\n<p>I have updated the files to create <a href=\"https:\/\/devopscube.com\/configure-multiple-kubernetes-clusters-argo-cd\/#:~:text=Argo%20CD%20cluster.-,Step%201%3A%20Create%20a%20Service%20Account,-First%2C%20log%20in\">RBAC objects<\/a> and a <a href=\"https:\/\/devopscube.com\/configure-multiple-kubernetes-clusters-argo-cd\/#:~:text=the%20required%20resources.-,Step%202%3A%20Create%20a%20Secret,-The%20next%20step\">token<\/a> in the GitHub repo.<\/p>\n<pre><code>- name: Save Token and CA Data to Environment Variables\n  run: |\n      TARGET_TOKEN=$(kubectl get -n kube-system secret\/argocd-manager-token -o jsonpath='{.data.token}' | base64 --decode)\n      \n      TARGET_CA_DATA=$(kubectl get -n kube-system secret\/argocd-manager-token -o jsonpath='{.data.ca\\.crt}')\n      \n      TARGET_CLUSTER_ENDPOINT=$(aws eks describe-cluster --name $CLUSTER_NAME --query \"cluster.endpoint\" --output text | sed 's~https:\/\/~~')\n\n      echo \"TARGET_TOKEN=$TARGET_TOKEN\" &gt;&gt; $GITHUB_ENV\n      echo \"TARGET_CA_DATA=$TARGET_CA_DATA\" &gt;&gt; $GITHUB_ENV\n      echo \"TARGET_CLUSTER_ENDPOINT=$TARGET_CLUSTER_ENDPOINT\" &gt;&gt; $GITHUB_ENV<\/code><\/pre>\n<p>This step will get and save the token and clusters CA certificate as environment variables.<\/p>\n<p>Also, it saves the cluster endpoint in <code>$TARGET_CLUSTER_ENDPOINT<\/code> a variable using the AWS command specified in it<\/p>\n<p>If you are using other cloud clusters, change the command accordingly.<\/p>\n<p>You can see I have used the <code>SED<\/code> command at the end of the cluster endpoint command to remove it <code>https:\/\/<\/code> from the endpoint while saving it as a variable.<\/p>\n<p>You will understand why I removed <code>https:\/\/<\/code> it before saving the cluster endpoint in the final step.<\/p>\n<pre><code>- name: Update kubeconfig file to Dev Cluster\n  run: |\n    aws eks update-kubeconfig --name dev-cluster --region us-east-1<\/code><\/pre>\n<p>This command updates the <a href=\"https:\/\/devopscube.com\/kubernetes-kubeconfig-file\/\" rel=\"noreferrer\">kubeconfig file<\/a> to use the cluster dev-cluster, where ArgoCD is installed.<\/p>\n<p>Update the <code>cluster name<\/code> and <code>region<\/code>, in the above step.<\/p>\n<pre><code>- name: Add Target Cluster to ArgoCD\n  run: |\n\n    cat &lt;&lt;EOF | kubectl apply -n argocd -f -\n    apiVersion: v1\n    kind: Secret\n    metadata:\n      name: ${{ github.event.inputs.environment }}-cluster-secret\n      labels:\n        argocd.argoproj.io\/secret-type: cluster\n    type: Opaque\n    stringData:\n      name: ${{ github.event.inputs.environment }}-cluster\n      server: https:\/\/$TARGET_CLUSTER_ENDPOINT\n      config: |\n        {\n          \"bearerToken\": \"$TARGET_TOKEN\",\n          \"tlsClientConfig\": {\n            \"serverName\": \"$TARGET_CLUSTER_ENDPOINT\",\n            \"caData\": \"$TARGET_CA_DATA\"\n          }\n        }\n    EOF<\/code><\/pre>\n<p>This is the final step, which creates a secret on the argocd namespace with the token, CA certificate, and Cluster endpoint.<\/p>\n<p>This secret is the configuration for ArgoCD to connect with a new cluster.<\/p>\n<p>It uses the cluster endpoint to connect, a token, and CA data for authentication and secure connection.<\/p>\n<p>You can see it <code>$TARGET_CLUSTER_ENDPOINT<\/code> is used in two places in the above command.<\/p>\n<p>One with <code>https:\/\/<\/code> and another without it, this is the reason I saved the cluster endpoint without <code>https:\/\/<\/code> using the SED command<\/p>\n<h2 id=\"trigger-the-workflow\">Trigger the Workflow<\/h2>\n<p>Now, it&#8217;s time to trigger the workflow.<\/p>\n<p>Since we have only configured a manual trigger, you have to go to Actions, select the workflow name, click the Run workflow toggle button, select the environment, and click the Run workflow button as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/04\/image-72.png\" class=\"kg-image\" alt=\"manual triggering workflow\" loading=\"lazy\" width=\"1010\" height=\"632\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/04\/image-72.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/04\/image-72.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/04\/image-72.png 1010w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Once you have triggered the workflow, you will see it start as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/04\/image-74.png\" class=\"kg-image\" alt=\"workflow starts running\" loading=\"lazy\" width=\"892\" height=\"397\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/04\/image-74.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/04\/image-74.png 892w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>If everything runs without any issues, you can see the workflow build as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/04\/image-73.png\" class=\"kg-image\" alt=\"workflow completed successfully\" loading=\"lazy\" width=\"896\" height=\"619\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/04\/image-73.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/04\/image-73.png 896w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Now, log in to your ArgoCD UI and go to <strong>Settings,<\/strong> the final step for workflows.<\/p>\n<h2 id=\"common-issues-and-troubleshooting\">Common Issues and Troubleshooting<\/h2>\n<p>The only issue you may face in this workflow is that the runner cannot access the target cluster.<\/p>\n<p>If you face this issue, check the following:<\/p>\n<ol>\n<li>Check if the target clusters are created and running<\/li>\n<li>Check if the runner has permissions to access the cluster.<\/li>\n<li>Check if the target clusters&#8217; kubeconfig contexts are added to the runner.<\/li>\n<li>Check if the runner has permissions to access the cluster.<\/li>\n<li>Check that the region and cluster names on the workflow are correct.<\/li>\n<\/ol>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>That&#8217;s all for the automation workflow. With this, you don&#8217;t need to add new clusters to ArgoCD repeatedly.<\/p>\n<p>In this guide, you have learned about the workflow for automating cluster additions to ArgoCD, how the workflow works, and how to troubleshoot if a cluster is not added correctly.<\/p>\n<p>If you want to try the manual setup, follow this <a href=\"https:\/\/devopscube.com\/configure-multiple-kubernetes-clusters-argo-cd\/\">blog<\/a>.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/automate-adding-clusters-to-argocd\/\" target=\"_blank\" rel=\"noopener noreferrer\">Automate Adding Clusters to ArgoCD using GitHub Actions \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/automate-adding-clusters-to-argocd\/<\/p>\n","protected":false},"author":1,"featured_media":515,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-514","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/514","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=514"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/514\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/515"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=514"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=514"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=514"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}