Deploy workloads from Azure DevOps pipelines to private Amazon EKS clusters - AWS Prescriptive Guidance

Deploy workloads from Azure DevOps pipelines to private Amazon EKS clusters

Mahendra Revanasiddappa, Amazon Web Services

Summary

This pattern demonstrates how to implement continuous integration and continuous delivery (CI/CD) from Azure DevOps pipelines to private Amazon Elastic Kubernetes Service (Amazon EKS) clusters. It addresses a critical challenge faced by organizations that are enhancing their security posture by transitioning to private API server endpoints for their Amazon EKS clusters.

A public endpoint exposes the Kubernetes API server directly to the internet, creating a larger attack surface that malicious actors could potentially target. By switching to a private endpoint, access to the cluster's control plane is restricted to within the customer's virtual private cloud (VPC).

Although transitioning an Amazon EKS cluster to a private API endpoint significantly enhances security, it introduces connectivity challenges for external CI/CD platforms like Azure DevOps. The private endpoint is only accessible from within the cluster's VPC or peered networks. Therefore, standard Microsoft-hosted Azure DevOps agents, operating outside the AWS private network, can’t reach the Kubernetes API server directly. This breaks typical deployment workflows that rely on tools like kubectl or Helm running on these agents because they fail to establish a connection to the cluster.

To overcome this problem, this pattern showcases an efficient approach by using self-hosted Azure DevOps agents within private Amazon EKS clusters. This solution offers superior cost optimization, operational efficiency, and scalability while preserving security requirements. This approach particularly benefits enterprises seeking to streamline their multi-cloud DevOps processes without compromising on performance or security.

Prerequisites and limitations

Prerequisites

  • An active AWS account.

  • AWS Command Line Interface (AWS CLI) version 2.13.17 or later, installed.

  • kubectl version 1.25.1 or later, installed.

  • A private Amazon EKS cluster version 1.24 or later created, with permissions to create namespaces, secrets, and deployments.

  • Worker nodes in an Amazon EKS cluster with outbound connectivity to the internet so that the Azure DevOps agent running on them can connect to Azure DevOps agent pool.

  • GitHub account created.

  • An Azure DevOps project with access to configure service connections, which are authenticated connections between Azure Pipelines and external or remote services, created.

  • The AWS Toolkit for Azure DevOps version 1.15 or later installed for the Azure DevOps project described in the previous point. For installation instructions, see AWS Toolkit for Azure DevOps in Visual Studio Marketplace.

Limitations

Architecture

This pattern creates the following:

  • Amazon ECR repository - The Amazon Elastic Container Registry (Amazon ECR) repository stores the Docker image with the Azure DevOps agent and the sample app that is deployed.

  • Azure DevOps agent pool - An Azure DevOps self-hosted agent pool registers the agent running on the private Amazon EKS cluster.

  • IAM role - An AWS Identity and Access Management (IAM) role for the Azure service connection to provide required access to the agent that’s running on a private Amazon EKS cluster.

  • Azure DevOps service connection - A service connection in an Azure DevOps account to use the IAM role that provides the required access for the pipeline jobs to access AWS services.

The following diagram shows the architecture of deploying a self-hosted Azure DevOps agent on a private Amazon EKS cluster and deploying a sample application on the same cluster.

Deployment of self-hosted Azure DevOps agent and sample application on private Amazon EKS cluster.

The diagram shows the following workflow:

  1. Deploy a self-hosted Azure DevOps agent as a deployment inside an Amazon EKS cluster.

  2. An Azure DevOps agent connects to the agent pool on an Azure DevOps account using a personal access token (PAT) for authentication.

  3. Azure Pipelines configures a pipeline to deploy by using code from a GitHub repository.

  4. The pipeline runs on the agent from the agent pool that was configured in the pipeline configuration. The Azure DevOps agent gets the job information of the pipeline by constantly polling to the Azure DevOps account.

  5. The Azure DevOps agent builds a Docker image as part of the pipeline job and pushes the image to the Amazon ECR repository.

  6. The Azure DevOps agent deploys the sample application on a private Amazon EKS cluster in a namespace called webapp.

Tools

Tools

Other tools

  • Docker is a set of platform as a service (PaaS) products that use virtualization at the operating-system level to deliver software in containers.

  • kubectl is a command-line interface that helps you run commands against Kubernetes clusters.

Code repository

Best practices

Epics

TaskDescriptionSkills required

Find the Azure DevOps organization GUID.

Sign in to your Azure DevOps account, and then use the following URL to find the organization GUID: https://dev.azure.com/{DevOps_Org_ID}/_apis/projectCollections?api-version=6.0 In the URL, replace {DevOps_org_ID} with your Azure DevOps organization ID.

AWS DevOps

Configure an IdP in the AWS account.

To configure an Identity provider (IdP) in the AWS account for an Azure service connection, use the following steps:

  1. Sign in to the AWS Management Console, and open the IAM console at https://console.aws.amazon.com/iam/.

  2. In the left pane, choose Identity providers.

  3. Choose Add Provider.

  4. Choose OpenID Connect as the Provider type.

  5. For Provider URL, enter the Azure DevOps issuer URL. Each tenant of Azure DevOps has a unique OrganizationGUID that typically uses the following format: https://vstoken.dev.azure.com/{OrganizationGUID} Replace {OrganizationGUID} with your Azure DevOps organization ID.

  6. For Audience, enter api://AzureADTokenExchange. This is a fixed value for Azure DevOps.

  7. Choose Add Provider.

  8. Take note of the ARN of the newly created provider for your use in the next task.

For more details, see How to federate into AWS from Azure DevOps using OpenID Connect.

AWS DevOps

Create an IAM policy in the AWS account.

To create an IAM policy to provide the required permissions to the IAM role used by the Azure DevOps pipeline, use the following steps:

  1. In the IAM console, in the left pane, choose Policies.

  2. Choose Create Policy.

  3. For Specify Permissions, in Policy editor, select JSON. Replace the default JSON policy with the following JSON:

    { "Version": "2012-10-17", "Statement": [ { "Sid": "Statement1", "Effect": "Allow", "Action": [ "ecr:*", "eks:DescribeCluster", "eks:ListClusters" ], "Resource": "*" } ] }
  4. Choose Next.

  5. For Policy name, enter a name for the IAM policy. This pattern uses the name ADO-policy.

  6. Choose Create policy.

AWS DevOps

Create an IAM role in the AWS account.

To configure an IAM role in the AWS account for the Azure service connection, use the following steps:

  1. In the IAM console, in the left pane, choose Roles.

  2. Choose Create role.

  3. For Trusted entity type, select Web Identity.

  4. Select the correct IdP from the dropdown list. The IdP name starts with vstoken.dev.azure.com/{OrganizationGUID}.

  5. In the Audience dropdown list, select api://AzureADTokenExchange.

  6. To limit this role to only one service connection, add a condition. Under Condition, select Add condition, and for Key, select vstoken.dev.azure.com/{OrganizationGUID}:sub. In Condition, select StringEquals. For Value, use the following format: sc://{OrganizationName}/{ProjectName}/{ServiceConnectionName}. For ServiceConnectionName, use aws-sc. You will create this service connection in the next task.

  7. Choose Next.

  8. For Add permissions, select ADO-policy, which is the policy that you created in a previous task.

  9. Choose Next, and for Role name, enter ado-role. For Select Trusted Entities, use the following trust policy:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::{account_id}:oidc-provider/vstoken.dev.azure.com/{OrganizationGUID}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "vstoken.dev.azure.com/{OrganizationGUID}:aud": "api://AzureADTokenExchange", "vstoken.dev.azure.com/{OrganizationGUID}:sub": "sc://{OrganizationName}/{ProjectName}/{ServiceConnectionName}" } } } ] }

In the policy, provide your information for the following placeholders:

  • {account_id} - AWS account ID

  • {OrganizationGUID} - Azure DevOps Organization GUID

  • {OrganizationName} - Azure DevOps Organization name

  • {ProjectName} - Azure DevOps project name

  • {ServiceConnectionName} - Azure DevOps service connection name. Use aws-sc. You will create this service connection in the next task.

AWS DevOps

Create a service connection in the Azure DevOps account.

To configure an Azure service connection, use the following steps:

  1. In your Azure DevOps project, select Project settings, Service connections.

  2. Choose New service connection, select the type of service connection as aws, and then select Next.

  3. For Role to Assume, enter the arn for the IAM role ado-role. You created ado-role in the previous task Create an IAM role in the AWS account.

  4. Select the Use OIDC checkbox.

  5. For Service connection name, enter aws-sc in task properties.

  6. Choose Save.

For more details, see Create a service connection in the Microsoft documentation.

AWS DevOps

Add IAM role to Amazon EKS configuration file.

The IAM role must have the necessary permissions to perform the required operations on the Amazon EKS cluster. Because it’s a pipeline role, the IAM role must be able to manage almost all types of resources on the cluster. Therefore, the system:masters group permission is appropriate for this role.

To add the required configuration to the aws-auth ConfigMap within Kubernetes, use the following code:

- groups: - system:masters rolearn: arn:aws:iam::{account_id}:role/ADO-role username: ADO-role

Replace {account_id} with your AWS account ID.

For more information, see How Amazon EKS works with IAM in the Amazon EKS documentation.

AWS DevOps
TaskDescriptionSkills required

Create a self-hosted agent pool.

To configure a self-hosted agent pool in the Azure DevOps account, use the following steps:

  1. Sign in to your Azure DevOps account organization.

  2. Choose Azure DevOps Organization.

  3. Choose your Azure DevOps project.

  4. Choose Project settings.

  5. Choose Agent pools.

  6. Choose Add pool.

  7. Select Self-hosted.

  8. For Name, enter eks-agent.

  9. Select the Grant access permission to all pipelines checkbox.

  10. Choose Create.

For more details, see Create and manage agent pools in the Microsoft documentation.

TaskDescriptionSkills required

Create an Amazon ECR repository.

The Docker images that are used to deploy the Azure DevOps agent and sample application (webapp) on the private Amazon EKS cluster must be stored in an Amazon ECR repository. To create an Amazon ECR repository, use the following steps:

  1. Open the Amazon ECR console at https://console.aws.amazon.com/ecr/repositories.

  2. From the navigation bar, choose the AWS Region to create your repository in.

  3. On the Repositories page, choose Private repositories, and then choose Create repository.

  4. For Repository name, enter webapp. For the sample application in this pattern to work, the Amazon ECR repository name must use webapp. If you use a different name for the repository, see Troubleshooting.

For more details, see Creating an Amazon ECR private repository to store images in the Amazon ECR documentation.

AWS DevOps

Create a Dockerfile to build the Azure DevOps agent.

Create a Dockerfile to build the Docker image that has the Azure DevOps agent installed. Store the following content in a file named Dockerfile:

FROM ubuntu:22.04 ENV TARGETARCH="linux-x64" RUN apt update && apt upgrade -y && apt install -y curl git jq libicu70 unzip wget RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" RUN unzip awscliv2.zip RUN ./aws/install RUN rm -rf aws awscliv2.zip RUN curl -sSL https://get.docker.com/ | sh RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash RUN mkdir -p azp WORKDIR /azp/ COPY ./start.sh ./ RUN chmod +x ./start.sh RUN useradd -m -d /home/agent agent RUN chown -R agent:agent /azp /home/agent RUN groupadd -f docker RUN usermod -aG docker agent USER agent ENTRYPOINT [ "./start.sh" ]
AWS DevOps

Create script for the Azure DevOps agent.

To create the start.sh script, use the following steps:

  1. Go to the procedure Create and build the Dockerfile in the Microsoft documentation, and scroll to step 5. Save the following content to ~/azp-agent-in-docker/start.sh, making sure to use Unix-style (LF) line endings.

  2. Copy the script’s content, and save it in a file named start.sh in the same directory as the Dockerfile.

AWS DevOps

Build a Docker image with the Azure DevOps agent.

To create a Docker image to install the Azure DevOps agent, use the Dockerfile that you created earlier to build the image. In the same directory where the Dockerfile is stored, run the following commands:

aws ecr get-login-password --region region | docker login --username AWS --password-stdin aws_account_id.dkr.ecr.region.amazonaws.com docker build --platform linux/amd64 -t ado-agent:latest . docker tag ado-agent:latest aws_account_id.dkr.ecr.region.amazonaws.com/webapp:latest docker push aws_account_id.dkr.ecr.region.amazonaws.com/webapp:latest

Replace aws_account_id and region with your AWS account ID and AWS Region.

AWS DevOps
TaskDescriptionSkills required

Generate an Azure personal access token.

The agent running on the private Amazon EKS cluster requires a personal access token (PAT) so that it can authenticate with the Azure DevOps account. To generate a PAT, use the following steps:

  1. Sign in with the user account you plan to use in your Azure DevOps organization (https://dev.azure.com/{Your_Organization}).

  1. From your home page, open your user settings, and then select Personal access tokens.

  2. Choose New Token.

  3. Enter the Name for the token.

  4. Choose Show all scopes.

  5. For Agent Pools, select the Read & manage checkbox.

  6. Choose Create.

  7. To create a secret on the private Amazon EKS cluster, use the following configuration:

apiVersion: v1 kind: Secret metadata: name: azdevops-pat namespace: default type: Opaque stringData: AZP_TOKEN: <PAT Token>
  1. Store the configuration in a file named ado-secret.yaml. Replace <PAT Token> with the personal access token that you just created. To create the secret, run the following command:

kubectl create -f ado-secret.yaml

For more details, see Register an agent using a personal access token (PAT) in the Microsoft documentation.

AWS DevOps

Use the Kubernetes manifest file for agent deployment.

To deploy the Azure DevOps agent on the private Amazon EKS cluster, copy the following manifest file and store the file as agent-deployment.yaml:

apiVersion: apps/v1 kind: Deployment metadata: name: azure-pipelines-agent-eks labels: app: azure-pipelines-agent spec: replicas: 1 selector: matchLabels: app: azure-pipelines-agent template: metadata: labels: app: azure-pipelines-agent spec: containers: - name: docker image: docker:dind securityContext: privileged: true volumeMounts: - name: shared-workspace mountPath: /workspace - name: dind-storage mountPath: /var/lib/docker env: - name: DOCKER_TLS_CERTDIR value: "" - name: azure-pipelines-agent image: aws_account_id.dkr.ecr.region.amazonaws.com/webapp:latest env: - name: AZP_URL value: "<Azure account URL>" - name: AZP_POOL value: "eks-agent" - name: AZP_TOKEN valueFrom: secretKeyRef: name: azdevops-pat key: AZP_TOKEN - name: AZP_AGENT_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: DOCKER_HOST value: tcp://localhost:2375 volumeMounts: - mountPath: /workspace name: shared-workspace volumes: - name: dind-storage emptyDir: {} - name: shared-workspace emptyDir: {}

Replace aws_account_id and <Azure account URL> with your AWS account ID and Azure DevOps account URL.

AWS DevOps

Deploy the agent on the private Amazon EKS cluster.

To deploy the Azure Devops agent on the private Amazon EKS cluster, use the following command:

kubectl create -f agent-deployment.tf
AWS DevOps

Verify the agent is running.

To verify that the Azure DevOps agent is running, use the following command:

kubectl get deploy azure-pipelines-agent-eks

The expected output should be similar to the following:

NAME READY UP-TO-DATE AVAILABLE AGE azure-pipelines-agent-eks 1/1 1 1 58s

Make sure that the READY column shows 1/1.

AWS DevOps

Verify the agent is registered with the Azure DevOps agent pool.

To verify that the agent is deployed on the private Amazon EKS cluster and is registered with the agent pool eks-agent, use the following steps:

  1. Sign in to your Azure DevOps organization (https://dev.azure.com/{Your_Organization}).

  2. Choose Project settings.

  3. Choose Agent pools.

  4. Select the eks-agent pool, and then check the Agents tab.

You should see one agent listed with a Status of Online, and the name of the agent should start with azure-pipelines-agent-eks-*.

AWS DevOps
TaskDescriptionSkills required

Fork the sample application repository to your GitHub account.

Fork the following AWS Samples repository to your GitHub account:

https://github.com/aws-samples/deploy-kubernetes-resources-to-amazon-eks-using-azure-devops

AWS DevOps

Create a pipeline.

To create a pipeline in your Azure DevOps account, use the following steps:

  1. Sign in with the user account you plan to use in your Azure DevOps organization (https://dev.azure.com/{Your_Organization}).

  2. Navigate to your project and pipelines console.

  3. Choose New Pipeline.

  4. For Where is your code, choose GitHub.

  5. Provide the credentials required for the pipeline to connect to your GitHub account

  6. Select the repository deploy-kubernetes-resources-to-amazon-eks-using-azure-devops.

  7. For Configure your pipeline, select Existing Azure Pipelines YAML file.

  8. For Select an existing YAML file, select main for Branch and azure_pipelines.yaml for path.

  9. Choose Continue.

  10. For Review your pipeline YAML, replace the input parameter values for awsRegion and awsEKSClusterName with your information:

pool: name: eks-agent #pool: self-hosted # If you are running self-hosted Azure DevOps Agents stages: # Refering the pipeline template, input parameter that are not specified will be added with defaults - template: ./pipeline_templates/main_template.yaml parameters: serviceConnectionName: aws-sc awsRegion: <your region> awsEKSClusterName: <name of your EKS cluster> projectName: webapp
  1. Choose RUN.

AWS DevOps

Verify that the sample application deployed.

After the pipeline completes, verify the successful deployment of the sample application by checking both the Amazon ECR repository and the Amazon EKS cluster.

To verify artifacts in the Amazon ECR repository, use the following steps:

  1. Navigate to the webapp Amazon ECR repository.

  2. Confirm the presence of the following new artifacts:

  • Docker image – <date>.<build_number>-image

  • Helm chart – <date>.<build_number>-helm

For example, 20250501.1-image and 20250501.1-helm.

To verify deployment on the private Amazon EKS cluster in the namespace webapp, use the following command:

kubectl get deploy -n webapp

The expected output is as follows:

NAME READY UP-TO-DATE AVAILABLE webapp 1/1 1 1

Note: If this is your first pipeline run, you might need to authorize the service connection and agent pool. Look for permission requests in the Azure DevOps pipeline interface, and approve them to proceed.

AWS DevOps

Troubleshooting

IssueSolution

Pipeline fails when Amazon ECR repository name doesn’t match webapp

The sample application expects the Amazon ECR repository name to match the projectName: webapp parameter in azure_pipeline.yml.

To resolve this issue, rename your Amazon ECR repository to webapp, or update the following:

  • Rename the webapp directory in your forked GitHub repository to match your Amazon ECR repository name.

  • Update the projectName parameter in azure_pipeline.yml to match your Amazon ECR repository name.

Error: Kubernetes cluster unreachable: the server has asked for the client to provide credentials

If you encounter this error in the "Pull and Deploy Helm Chart" step in your Azure pipeline, the root cause typically stems from an incorrect IAM role configuration in your Amazon EKS cluster's aws-auth ConfigMap.

To resolve this issue, check the following:

  • Verify your aws-auth ConfigMap configuration.

  • Check your Amazon EKS cluster's authentication settings: Open the Amazon EKS console, Cluster details, Access configuration. Make sure that Authentication mode is set to EKS API and ConfigMap (not just EKS API).

Related resources

AWS Blogs

AWS services documentation

Microsoft documentation