# Elastic Kubernetes Service (EKS)

Source: /aws/services/eks/

## Introduction

Elastic Kubernetes Service (EKS) is a managed Kubernetes service that makes it easy to run Kubernetes on AWS without installing, operating, and maintaining your own Kubernetes control plane or worker nodes.
Kubernetes is an open-source system for automating containerized applications' deployment, scaling, and management.

LocalStack allows you to use the EKS APIs in your local environment to spin up embedded Kubernetes clusters in your local Docker engine or use an existing Kubernetes installation you can access from your local machine (defined in `$HOME/.kube/config`).
The supported APIs are available on our [API Coverage section](#api-coverage), which provides information on the extent of EKS's integration with LocalStack.

## Getting started

This guide is designed for users new to Elastic Kubernetes Service and assumes basic knowledge of the AWS CLI and our [`lstk aws`](/aws/developer-tools/running-localstack/lstk/cloud-and-iac-commands/#aws) command.
To interact with the Kubernetes cluster, you should also install [`kubectl`](https://kubernetes.io/docs/tasks/tools/).

Start your LocalStack container using your preferred method.
We will demonstrate how you can auto-install an embedded Kubernetes cluster, configure ingress, and deploy a sample service with ECR.

### Deploy the necessary networking components

First we need to create a VPC for the EKS cluster. You can create a new VPC using the [`CreateVpc` API](https://docs.aws.amazon.com/vpc/latest/APIReference/API_CreateVpc.html).

Run the following command:

```bash title="Create VPC"
lstk aws ec2 create-vpc --cidr-block 10.0.0.0/16
```

```bash title="Output"
{
    "Vpc": {
        ...
        "CidrBlock": "10.0.0.0/16",
        "VpcId": "<vpc-id>",
        ...
    }
}
```

Next, we need to create a subnet in the VPC. You can create a 2 subnets using the [`CreateSubnet` API](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateSubnet.html). Some extra tags might be required for specific Controllers to work properly. Please refer to their specific documentation for more details.

Run the following command:

```bash title="Create Subnet 1"
lstk aws ec2 create-subnet \
  --vpc-id <vpc-id> \
  --cidr-block 10.0.1.0/24 \
  --availability-zone us-east-1a
```

```bash title="Output"
{
    "Subnet": {
        ...
        "SubnetId": "<subnet-id-1>",
        "VpcId": "<vpc-id>",
        "CidrBlock": "10.0.1.0/24"
        ...
    }
}
```

```bash title="Create Subnet 2"
lstk aws ec2 create-subnet \
  --vpc-id <vpc-id> \
  --cidr-block 10.0.2.0/24 \
  --availability-zone us-east-1b
```

```bash title="Output"
{
    "Subnet": {
        ...
        "SubnetId": "<subnet-id-2>",
        "VpcId": "<vpc-id>",
        "CidrBlock": "10.0.2.0/24"
        ...
    }
}
```

### Create an embedded Kubernetes cluster

The default approach for creating Kubernetes clusters using the local EKS API is by setting up an embedded [k3d](https://k3d.io/) kube cluster within Docker.
LocalStack seamlessly manages the download and installation process, making it hassle-free for users.
In most cases, the installation is automatic, eliminating the need for any manual customizations.

:::note
The Traefik ingress controller and the default k3d load balancer containers are no longer started automatically when creating an EKS cluster.  
To restore the previous behavior, set the following configuration variable:

```bash
K3D_START_LB_INGRESS=1
```
:::

:::note
If you run LocalStack with Docker Compose, the k3d containers backing an EKS cluster can be left behind as orphans if LocalStack is killed before it finishes shutting down. See [Why are some containers left behind after I stop LocalStack with Docker Compose?](/aws/getting-started/faq/#why-are-some-containers-left-behind-after-i-stop-localstack-with-docker-compose) for the cause and how to configure `stop_grace_period` and `SHUTDOWN_TIMEOUT` to avoid it.
:::

You can create a new cluster using the [`CreateCluster` API](https://docs.aws.amazon.com/eks/latest/APIReference/API_CreateCluster.html).

Run the following command:

```bash title="Create Cluster"
lstk aws eks create-cluster \
  --name cluster1 \
  --role-arn "arn:aws:iam::000000000000:role/eks-role" \
  --resources-vpc-config '{"subnetIds":["<subnet-id-1>", "<subnet-id-2>"]}'
```

```bash title="Output"
{
    "cluster": {
        "name": "cluster1",
        "arn": "arn:aws:eks:us-east-1:000000000000:cluster/cluster1",
        "createdAt": "2022-04-13T16:38:24.850000+02:00",
        "roleArn": "arn:aws:iam::000000000000:role/eks-role",
        "resourcesVpcConfig": {
            "subnetIds": [
                "<subnet-id-1>",
                "<subnet-id-2>"
            ]
        },
        "identity": {
            "oidc": {
                "issuer": "https://localhost.localstack.cloud/eks-oidc"
            }
        },
        "status": "CREATING",
        "clientRequestToken": "cbdf2bb6-fd3b-42b1-afe0-3c70980b5959"
    }
}
```

The cluster creation process may take a few moments as LocalStack sets up the necessary components. Avoid attempting to access the cluster until the status changes to `ACTIVE`.

Run the following command to wait for the cluster status to become `ACTIVE`:

```bash title="Wait for Cluster"
lstk aws eks wait cluster-active --name cluster1
```

:::note
When setting up a local EKS cluster, if you encounter a `"status": "FAILED"` in the command output and see `Unable to start EKS cluster` in LocalStack logs, remove or rename the `~/.kube/config` file on your machine and retry.
The CLI mounts this file automatically for CLI versions before `3.7`, leading EKS to assume you intend to use the specified cluster, a feature that has specific requirements.
:::

You can use the `docker` CLI to check that some containers have been created:

```bash
docker ps
```

```bash title="Output"
CONTAINER ID   IMAGE                          COMMAND                  CREATED          STATUS          PORTS                                           NAMES
...
b335f7f089e4   rancher/k3d-proxy:5.0.1-rc.1   "/bin/sh -c nginx-pr…"   1 minute ago   Up 1 minute   0.0.0.0:8081->80/tcp, 0.0.0.0:44959->6443/tcp   k3d-cluster1-serverlb
f05770ec8523   rancher/k3s:v1.21.5-k3s2       "/bin/k3s server --t…"   1 minute ago   Up 1 minute
...
```

### Creating a managed node group

The EKS cluster created in the previous step does not include any worker nodes by default. While you can inspect the server node, it is [tainted](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/), and workloads cannot be scheduled on it. To run workloads on the cluster, you must add at least one worker node. One way to do this is by creating a managed node group. When you create a managed node group, LocalStack automatically provisions a Docker container, joins it to the cluster, and provisions a mocked EC2 instance.

You can create a managed node group for your EKS cluster using the [`CreateNodegroup` API](https://docs.aws.amazon.com/eks/latest/APIReference/API_CreateNodegroup.html).

Run the following command:

```bash title="Create Node Group"
lstk aws eks create-nodegroup \
  --cluster-name cluster1 \
  --nodegroup-name nodegroup1 \
  --node-role arn:aws:iam::000000000000:role/eks-nodegroup-role \
  --subnets <subnet-id-1> <subnet-id-2> \
  --scaling-config desiredSize=1
```

```bash title="Output"
{
    "nodegroup": {
        "nodegroupName": "nodegroup1",
        "nodegroupArn": "arn:aws:eks:us-east-1:000000000000:nodegroup/cluster1/nodegroup1/xxx",
        "clusterName": "cluster1",
        "version": "1.21",
        "releaseVersion": "1.21.7-20220114",
        "createdAt": "2022-04-13T17:25:45.821000+02:00",
        "status": "CREATING",
        "capacityType": "ON_DEMAND",
        "scalingConfig": {
            "desiredSize": 1
        },
        "subnets": [
            "<subnet-id-1>",
            "<subnet-id-2>"
        ],
        "nodeRole": "arn:aws:iam::000000000000:role/eks-nodegroup-role",
        "labels": {},
        "health": {
            "issues": []
        },
        "updateConfig": {
            "maxUnavailable": 1
        }
    }
}
```

The node group creation process may take a few moments as LocalStack sets up the necessary components.

You can wait for the node group status to become `ACTIVE` by running the following command:

```bash title="Wait for Node Group"
lstk aws eks wait nodegroup-active --cluster-name cluster1 --nodegroup-name nodegroup1
```

At this point, your EKS cluster is fully operational and ready to deploy workloads.

### Utilizing ECR Images within EKS

You can now use ECR (Elastic Container Registry) images within your EKS environment.

#### Initial configuration

To modify the return value of resource URIs for most services, including ECR, you can utilize the `LOCALSTACK_HOST` variable in the [configuration](/aws/customization/configuration-options).
By default, ECR returns a `repositoryUri` starting with `localhost.localstack.cloud`, such as: `localhost.localstack.cloud:<port>/<repository-name>`.

:::note
In this section, we assume that `localhost.localstack.cloud` resolves in your environment, and LocalStack is connected to a non-default bridge network.
For more information, refer to the article about [DNS rebind protection](/aws/customization/networking/dns-server#dns-rebind-protection).

If the domain `localhost.localstack.cloud` does not resolve on your host, you can still proceed by setting `LOCALSTACK_HOST=localhost` (not recommended).

LocalStack will take care of the DNS resolution of `localhost.localstack.cloud` within ECR itself, allowing you to use the `localhost:<port>/<repository_name>` URI for tagging and pushing the image on your host.
:::

Once you have configured this correctly, you can seamlessly use your ECR image within EKS as expected.

#### Deploying a sample application from an ECR image

To showcase this behavior, let's go through a concise step-by-step guide that will lead us to the successful pulling of an image from local ECR.
For the purpose of this guide, we will retag the `nginx` image to be pushed to a local ECR repository under a different name, and then utilize it for a pod configuration.

You can create a new ECR repository using the [`CreateRepository` API](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_CreateRepository.html).
Run the following command:

```bash
lstk aws ecr create-repository --repository-name "fancier-nginx"
```

```bash title="Output"
{
    "repository": {
        "repositoryArn": "arn:aws:ecr:us-east-1:000000000000:repository/fancier-nginx",
        "registryId": "c75fd0e2",
        "repositoryName": "fancier-nginx",
        "repositoryUri": "000000000000.dkr.ecr.us-east-1.localhost.localstack.cloud:4566/fancier-nginx",
        "createdAt": "2022-04-13T14:22:47+02:00",
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": false
        },
        "encryptionConfiguration": {
            "encryptionType": "AES256"
        }
    }
}
```

You can now pull the `nginx` image from Docker Hub using the `docker` CLI:

```bash
docker pull nginx
```

You can further tag the image to be pushed to ECR:

```bash
docker tag nginx 000000000000.dkr.ecr.us-east-1.localhost.localstack.cloud:4566/fancier-nginx
```

Finally, you can push the image to local ECR:

```bash
docker push 000000000000.dkr.ecr.us-east-1.localhost.localstack.cloud:4566/fancier-nginx
```

Now, let us set up the EKS cluster using the image pushed to local ECR.

Next, we can configure `kubectl` to use the EKS cluster, using the [`UpdateKubeconfig` API](https://docs.aws.amazon.com/eks/latest/APIReference/API_UpdateClusterConfig.html).
Run the following command:

```bash
lstk aws eks update-kubeconfig --name cluster1 && \
    kubectl config use-context arn:aws:eks:us-east-1:000000000000:cluster/cluster1
```

```bash title="Output"
...
Added new context arn:aws:eks:us-east-1:000000000000:cluster/cluster1 to /home/localstack/.kube/config
Switched to context "arn:aws:eks:us-east-1:000000000000:cluster/cluster1".
...
```

You can now go ahead and add a deployment configuration for the `fancier-nginx` image.

```bash
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fancier-nginx
  labels:
    app: fancier-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fancier-nginx
  template:
    metadata:
      labels:
        app: fancier-nginx
    spec:
      containers:
      - name: fancier-nginx
        image: 000000000000.dkr.ecr.us-east-1.localhost.localstack.cloud:4566/fancier-nginx:latest
        ports:
        - containerPort: 80
EOF
```

You can now describe the pod to see if the image was pulled successfully:

```bash
kubectl describe pod fancier-nginx
```

In the events, we can see that the pull from ECR was successful:

```bash title="Output"
  Normal  Pulled     10s   kubelet            Successfully pulled image "000000000000.dkr.ecr.us-east-1.localhost.localstack.cloud:4566/fancier-nginx:latest" in 2.412775896s
```

:::tip
Public Docker images from `registry.k8s.io` can be pulled without additional configuration from EKS nodes, but if you pull images from any other locations that resolve to S3 you can configure `DNS_NAME_PATTERNS_TO_RESOLVE_UPSTREAM=<bucket-name>\.s3.*\.amazonaws\.com` in your [configuration](/aws/customization/configuration-options).
:::

### Configuring an Ingress for your services

To make an EKS service externally accessible, it is necessary to create an Ingress configuration, which exposes the service on a specific path to the load balancer.

For our sample deployment, we can create an `nginx` Kubernetes service by applying the following configuration:

```bash
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: fancier-nginx
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
EOF
```

Use the following ingress configuration to expose the `nginx` service on path `/test123`:

```bash
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
  annotations:
    ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
    - http:
        paths:
          - path: /test123
            pathType: Prefix
            backend:
              service:
                name: nginx
                port:
                  number: 80
EOF
```

You will be able to send a request to `nginx` via the load balancer port `8081` from the host:

```bash
curl http://localhost:8081/test123
```

```bash title="Output"
<html>
...
<hr><center>nginx/1.21.6</center>
...
```

:::tip
You can customize the Load Balancer port by configuring `EKS_LOADBALANCER_PORT` in your environment.
:::

### Enabling HTTPS with local SSL/TLS certificate for the Ingress

To enable HTTPS for your endpoints, you can configure Kubernetes to use SSL/TLS with the [certificate for local domain names](https://github.com/localstack/localstack-artifacts/blob/master/local-certs/server.key) `*.localhost.localstack.cloud`.

The local EKS cluster comes pre-configured with a secret named `ls-secret-tls`, which can be conveniently utilized to define the `tls` section in the ingress configuration:

```yaml showshowLineNumbers
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    ingress.kubernetes.io/ssl-redirect: "false"
    traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
spec:
  tls:
  - secretName: ls-secret-tls
    hosts:
    - myservice.localhost.localstack.cloud
  ...
```

Once you have deployed your service using the mentioned ingress configuration, it will be accessible via the HTTPS endpoint `https://myservice.localhost.localstack.cloud`.

Remember that the ingress controller does not support HTTP/HTTPS multiplexing within the same Ingress.
Consequently, if you want your service to be accessible via HTTP and HTTPS, you must create two separate Ingress definitions — one Ingress for HTTP and another for HTTPS.

:::note
The `ls-secret-tls` secret is created in the `default` namespace.
If your ingress and services are residing in a custom namespace, it is essential to copy the secret to that custom namespace to make use of it.
:::

## Self-managed nodes and Karpenter

In addition to [managed node groups](#creating-a-managed-node-group), LocalStack supports self-managed worker nodes: EC2 instances that join an EKS cluster on their own using the standard EKS bootstrap user-data.
This is the same mechanism that [Karpenter](https://karpenter.sh/) relies on to provision capacity, so you can run the full Karpenter node lifecycle (SSM AMI lookup, EC2 Fleet provisioning, node bootstrap, and scale-up/scale-down) against a local cluster without any LocalStack-specific configuration.

When an EC2 instance is launched with EKS bootstrap user-data, LocalStack parses it, connects the instance container to the cluster's internal network, and starts a [k3s](https://k3s.io/) agent inside it.
The instance then registers as a worker node, exactly as a real EKS node would appear to the control plane.

Two AMI families are supported:

- **Amazon Linux 2023 (AL2023)**: configuration is carried as a multipart MIME `application/node.eks.aws` NodeConfig document (used by `nodeadm` and Karpenter).
- **Bottlerocket**: configuration is carried as a plain TOML document under `[settings.kubernetes]`.

LocalStack reads the same set of fields from both formats and propagates them to the registered node, including node labels, taints, topology metadata (region, zone, instance type), and the provider ID. This matches what real EKS nodes look like from the cluster's perspective.

:::note
Self-managed nodes use the embedded k3d-backed provider (`MANAGED_K8S_PROVIDER=k3s`), which is the default.
The walkthrough below runs entirely between Docker containers, so it also works on macOS where direct host-to-instance networking is not available.
:::

### Resolving a node AMI

Self-managed nodes must be launched from an EKS-optimized AMI.
LocalStack resolves the standard EKS AMI [SSM public parameters](https://docs.aws.amazon.com/eks/latest/userguide/retrieve-ami-id.html) to a k3s-backed image, so you can look them up the same way Karpenter does.

<Tabs>
  <TabItem label="AL2023">
```bash title="Resolve AL2023 AMI"
lstk aws ssm get-parameter \
  --name /aws/service/eks/optimized-ami/1.35/amazon-linux-2023/x86_64/standard/recommended/image_id \
  --query 'Parameter.Value' --output text
```

```bash title="Output"
ami-eks-k3d-1.35-amd64-standard
```
  </TabItem>
  <TabItem label="Bottlerocket">
```bash title="Resolve Bottlerocket AMI"
lstk aws ssm get-parameter \
  --name /aws/service/bottlerocket/aws-k8s-1.35/x86_64/latest/image_id \
  --query 'Parameter.Value' --output text
```

```bash title="Output"
ami-eks-k3d-1.35-amd64
```
  </TabItem>
</Tabs>

The walkthrough below assumes a cluster named `cluster1` that is already `ACTIVE` (see [Create an embedded Kubernetes cluster](#create-an-embedded-kubernetes-cluster)).

### Joining a self-managed node

<Tabs>
  <TabItem label="AL2023">
    <Steps>

    1. Create a user-data file containing a `NodeConfig` document. At minimum, `spec.cluster.name` must match the name of your EKS cluster, which LocalStack uses to resolve which cluster the node should join. Optionally, you can set kubelet configuration and node labels:

       ```text title="al2023-userdata.txt"
       MIME-Version: 1.0
       Content-Type: multipart/mixed; boundary="//"

       --//
       Content-Type: application/node.eks.aws

       apiVersion: node.eks.aws/v1alpha1
       kind: NodeConfig
       spec:
         cluster:
           name: cluster1
         kubelet:
           config:
             maxPods: 20
             registerWithTaints:
             - key: dedicated
               value: gpu
               effect: NoSchedule
           flags:
           - '--node-labels=role=worker,env=demo'
       --//--
       ```

    2. Launch an EC2 instance using the resolved AL2023 AMI and the user-data file:

       ```bash title="Launch AL2023 node"
       lstk aws ec2 run-instances \
         --image-id ami-eks-k3d-1.35-amd64-standard \
         --count 1 \
         --instance-type t3.medium \
         --user-data file://./al2023-userdata.txt
       ```
    </Steps>
  </TabItem>

  <TabItem label="Bottlerocket">
    <Steps>

    1. Bottlerocket carries its bootstrap configuration as a plain TOML document. Create a user-data file with a `[settings.kubernetes]` section, setting `cluster-name` to your cluster:

       ```toml title="bottlerocket-userdata.toml"
       [settings.kubernetes]
       cluster-name = "cluster1"
       max-pods = 30

       [settings.kubernetes.node-labels]
       "role" = "worker"
       "env" = "demo"

       [settings.kubernetes.node-taints]
       "dedicated" = ["gpu:NoSchedule"]
       ```

    2. Launch an EC2 instance using the resolved Bottlerocket AMI:

       ```bash title="Launch Bottlerocket node"
       lstk aws ec2 run-instances \
         --image-id ami-eks-k3d-1.35-amd64 \
         --count 1 \
         --instance-type m5.large \
         --user-data file://./bottlerocket-userdata.toml
       ```
    </Steps>
  </TabItem>
</Tabs>

### Verifying the node

Point `kubectl` at the cluster and list the nodes:

```bash
lstk aws eks update-kubeconfig --name cluster1 && \
  kubectl config use-context arn:aws:eks:us-east-1:000000000000:cluster/cluster1
```

After a few seconds, the self-managed instance registers and becomes `Ready` alongside the cluster's control-plane node.
The node name is the EC2 instance ID:

```bash
kubectl get nodes
```

```bash title="Output"
NAME                                   STATUS   ROLES           AGE   VERSION
i-8a2eb615ddf838df7                    Ready    <none>          33s   v1.35.5+k3s1
k3d-cluster1-c2fad0d2-server-0         Ready    control-plane   2m    v1.35.5+k3s1
```

You can confirm that the configuration from the user-data was applied to the node:

```bash
kubectl get node i-8a2eb615ddf838df7 \
  -o jsonpath='{.spec.providerID}{"\n"}{.spec.taints}{"\n"}{.status.allocatable.pods}{"\n"}'
```

```bash title="Output"
aws:///us-east-1a/i-8a2eb615ddf838df7
[{"effect":"NoSchedule","key":"dedicated","value":"gpu"}]
20
```

### Supported configuration fields

LocalStack reads the following fields from the node bootstrap user-data and reflects them on the registered Kubernetes node.
The AL2023 and Bottlerocket columns show where each value comes from in the respective format:

| Behaviour                          | AL2023 (`NodeConfig`)                  | Bottlerocket (TOML)                       |
|:-----------------------------------|:---------------------------------------|:------------------------------------------|
| Target cluster                     | `spec.cluster.name`                    | `cluster-name`                            |
| Cluster DNS                        | `kubelet.config.clusterDNS`            | `cluster-dns-ip`                          |
| Max pods                           | `kubelet.config.maxPods`               | `max-pods`                                |
| Eviction thresholds                | `kubelet.config.evictionHard`          | `[settings.kubernetes.eviction-hard]`     |
| Node taints                        | `kubelet.config.registerWithTaints`    | `[settings.kubernetes.node-taints]`       |
| Node labels                        | `kubelet.flags` (`--node-labels`)      | `[settings.kubernetes.node-labels]`       |

In addition, LocalStack always emits the topology labels `topology.kubernetes.io/region`, `topology.kubernetes.io/zone`, and `node.kubernetes.io/instance-type` (derived from the instance's placement and type), as well as the provider ID in the form `aws:///<az>/<instance-id>`.

:::note
Fields such as `apiServerEndpoint` and `certificateAuthority` are required when bootstrapping against real EKS, but are resolved internally by LocalStack and can be omitted from the user-data.
:::

### Using Karpenter

Because self-managed nodes rely only on standard EC2 and EKS bootstrap behaviour, [Karpenter](https://karpenter.sh/) can drive node provisioning against a local cluster without any LocalStack-specific changes.
Karpenter looks up node AMIs through the SSM parameters described above, launches instances via EC2 Fleet with the appropriate NodeConfig or Bottlerocket user-data, and the new instances join the cluster as described.

To try this out, follow the upstream [Getting started with Karpenter](https://karpenter.sh/docs/getting-started/getting-started-with-karpenter/) guide, pointing the AWS endpoints at LocalStack.

## Use an existing Kubernetes installation

You can also access the EKS API using your existing local Kubernetes installation.
This can be achieved by setting the configuration variable `MANAGED_K8S_PROVIDER=local` and mounting the `$HOME/.kube/config` file into the LocalStack container.
When using a `docker-compose.yml` file, you need to add a bind mount like this:

```yaml
volumes:
  - "${HOME}/.kube/config:/root/.kube/config"
```

When using `lstk`, add the mount and provider variable to your `config.toml`:

```toml
[[containers]]
type = "aws"
volumes = ["~/.kube/config:/root/.kube/config"]
env     = ["k8s-provider"]

[env.k8s-provider]
MANAGED_K8S_PROVIDER = "local"
```

Then start LocalStack:

```bash
lstk start
```

:::note
Using an existing Kubernetes installation is currently only possible when the authentication with the cluster uses X509 client certificates: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certificates
:::

In recent versions of Docker, you can enable Kubernetes as an embedded service running inside Docker.
The picture below illustrates the Kubernetes settings in Docker for macOS (similar configurations apply for Linux/Windows).
By default, the Kubernetes API is assumed to run on the local TCP port `6443`.

![Kubernetes in Docker](/images/aws/kubernetes.png)

You can create an EKS Cluster configuration using the following command:

```bash
lstk aws eks create-cluster --name cluster1 --role-arn arn:aws:iam::000000000000:role/eks-role --resources-vpc-config '{}'
```

```bash title="Output"
{
    "cluster": {
        "name": "cluster1",
        "arn": "arn:aws:eks:eu-central-1:000000000000:cluster/cluster1",
        "createdAt": "Sat, 05 Oct 2019 12:29:26 GMT",
        "endpoint": "https://172.17.0.1:6443",
        "status": "ACTIVE",
        ...
    }
}
```

And check that it was created with:

```bash
lstk aws eks list-clusters
```

```bash title="Output"
{
    "clusters": [
        "cluster1"
    ]
}
```

To interact with your Kubernetes cluster, configure your Kubernetes client (such as `kubectl` or other SDKs) to point to the `endpoint` provided in the `create-cluster` output mentioned earlier.
However, depending on whether you're calling the Kubernetes API from your local machine or from within a Lambda function, you might need to use different endpoint URLs.

For local machine interactions, use `https://localhost:6443` as the endpoint URL.
If you are accessing the Kubernetes API from within a Lambda function, you should use `https://172.17.0.1:6443` as the endpoint URL, assuming that `172.17.0.1` is the IP address of the Docker network bridge.

By using the appropriate endpoint URL based on your context, you can effectively communicate with your Kubernetes cluster and manage your resources as needed.

## Customizing the Kubernetes Load Balancer Ports

By default, the Kubernetes load balancer (LB) is exposed on port `8081`.
If you need to customize the port or expose the load balancer on multiple ports, you can utilize the special tag name `_lb_ports_` during the cluster creation process.

For instance, if you want to expose the load balancer on ports 8085 and 8086, you can use the following tag definition when creating the cluster:

```bash
lstk aws eks create-cluster \
  --name cluster1 \
  --role-arn arn:aws:iam::000000000000:role/eks-role \
  --resources-vpc-config '{}' --tags '{"_lb_ports_":"8085,8086"}'
```

## Routing Traffic to Services on Different Endpoints

When working with EKS, a common scenario is to access multiple Kubernetes services behind different endpoints.

For instance, you might have multiple microservices, each following a common path versioning scheme, such as API request paths starting with `/v1/...`.
In such cases, path-based routing may not be ideal if you need the services to be accessible in a uniform manner.

To address this requirement, we recommend utilizing host-based routing rules, as demonstrated in the example below:

```bash showshowLineNumbers
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-services
  annotations:
    ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
  - host: eks-service-1.localhost.localstack.cloud
    http:
      paths:
      - path: /v1
        pathType: Prefix
        backend:
          service:
            name: service-1
            port:
              number: 80
  - host: eks-service-2.localhost.localstack.cloud
    http:
      paths:
      - path: /v1
        pathType: Prefix
        backend:
          service:
            name: service-2
            port:
              number: 80
EOF
```

The example defines routing rules for two local endpoints - the first rule points to a service `service-1` accessible under `/v1`, and the second rule points to a service `service-2` accessible under the same path `/v1`.

In the provided example, we define routing rules for two local endpoints.
The first rule directs traffic to a service named `service-1`, accessible under the path `/v1`.
Similarly, the second rule points to a service named `service-2`, also accessible under the same path `/v1`.

This approach enables us to access the two distinct services using the same path and port number, but with different host names.
This host-based routing mechanism ensures that each service is uniquely identified based on its designated host name, allowing for a uniform and organized way of accessing multiple services within the EKS cluster.

```bash
curl http://eks-service-1.localhost.localstack.cloud:8081/v1
```

```bash title="Output"
... [output of service 1]
```

```bash
curl http://eks-service-2.localhost.localstack.cloud:8081/v1
```

```bash title="Output"
... [output of service 2]
```

It is important to note that the host names `eks-service-1.localhost.localstack.cloud` and `eks-service-2.localhost.localstack.cloud` both resolve to `127.0.0.1` (localhost).
Consequently, you can utilize them to communicate with your service endpoints and distinguish between different services within the Kubernetes load balancer.

However, it might encounter issues in scenarios where you intend to run your Load Balancer (LB) on standard ports such as 80/443 since some of these ports may already be occupied on your local machine.
For instance, by default, LocalStack allocates port 443 to expose APIs via the HTTPS endpoint (`https://localhost.localstack.cloud`).
Hence, it's crucial to ensure that you expose your LB on a custom, non-standard port to prevent conflicts.

Additionally, note that LocalStack EKS employs [Traefik](https://doc.traefik.io/traefik/providers/kubernetes-ingress) as the Kubernetes ingress controller internally.

## Mounting directories from host to pod

If you have specific directories which you want to mount from your local dev machine into one of your pods, you can do this with two simple steps:

If you have specific directories that you want to mount from your local development machine into one of your pods, you can achieve this in two simple steps.

When creating your cluster, include the special tag `_volume_mount_`, which allows you to define the desired volume mounting configuration from your local development machine to the cluster nodes.

```bash
lstk aws eks create-cluster \
  --name cluster1 \
  --role-arn arn:aws:iam::000000000000:role/eks-role \
  --resources-vpc-config '{}' \
  --tags '{"_volume_mount_":"/path/on/host:/path/on/node"}'
```

```bash title="Output"
{
    "cluster": {
        "name": "cluster1",
        "arn": "arn:aws:eks:eu-central-1:000000000000:cluster/cluster1",
        "createdAt": "Sat, 05 Oct 2019 12:29:26 GMT",
        "endpoint": "https://172.17.0.1:6443",
        "status": "ACTIVE",
        "tags": {
            "_volume_mount_" : "/path/on/host:/path/on/node"
        }
        ...
    }
}
```

After creating your cluster with the `_volume_mount_` tag, you can create your path with volume mounts as usual.
The configuration for the volume mounts can be set up similar to this:

```yaml showshowLineNumbers
apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  volumes:
    - name: example-volume
      hostPath:
         path: /path/on/node
  containers:
  - image: alpine:3.12
    command: ["/bin/sh","-c"]
    args:
      - echo "Starting the update command";
        apk update;
        echo "Adding the openssh command";
        apk add openssh;
        echo "openssh completed";
        sleep 240m;
    imagePullPolicy: IfNotPresent
    name: alpine
    volumeMounts:
      - mountPath: "/path/on/pod"
        name: example-volume
  restartPolicy: Always
```

## Supported Versions

LocalStack uses [k3s](https://github.com/k3s-io/k3s) under the hood for creating EKS clusters.
Below is the list of supported Kubernetes versions and their corresponding k3s versions.

The default version is `1.36`.

| Kubernetes Version   | k3s Version   | EKS Platform Version   |
|----------------------|---------------|------------------------|
| 1.36                 | v1.36.2-k3s1  | eks.7                  |
| 1.35                 | v1.35.6-k3s1  | eks.18                 |
| 1.34                 | v1.34.9-k3s1  | eks.28                 |
| 1.33                 | v1.33.13-k3s1 | eks.42                 |
| 1.32                 | v1.32.13-k3s1 | eks.49                 |
| 1.31                 | v1.31.14-k3s1 | eks.65                 |
| 1.30                 | v1.30.14-k3s2 | eks.73                 |

Users can specify the desired version when creating an EKS cluster in LocalStack using the `K3S_IMAGE_TAG` configuration variable when starting LocalStack.

## Configuring the k3d Cluster Token

When LocalStack creates a k3d-backed EKS cluster, it starts the k3s server with an explicit cluster token. This token is used to authenticate agent nodes joining the cluster, enabling dynamic node registration.
By default, LocalStack uses `localstack-k3d-cluster-token` as the cluster token. You can override this value using the `K3D_CLUSTER_TOKEN` configuration variable:

```toml
# .lstk/config.toml
[[containers]]
type = "aws"
env  = ["k3d-token"]

[env.k3d-token]
EKS_K3D_CLUSTER_TOKEN = "my-custom-token"
```

```bash
lstk start
```

Any agent nodes added to the cluster — whether via k3d node create or k3s agent — will use the same token to authenticate with the k3s server.

## Resource Browser

The LocalStack Web Application provides a Resource Browser for managing EKS clusters.
You can access the Resource Browser by opening the LocalStack Web Application in your browser, navigating to the **Resource Browser** section, and then clicking on **EKS** under the **Compute** section.

![EKS Resource Browser](/images/aws/eks-resource-browser.png)

The Resource Browser allows you to perform the following actions:

- **Create Cluster**: Create a new EKS cluster by clicking on the **Create Cluster** button and providing the cluster name among other details.
- **View Cluster Details**: View the details of an existing EKS cluster by clicking on the cluster name.
- **Edit Cluster**: Edit the configuration of an existing EKS cluster by clicking on the **Edit** button while viewing the cluster details.
- **Delete Cluster**: Select the cluster name and click on the **Actions** button followed by **Remove Selected** button.

## API Coverage


### Elastic Kubernetes Service API coverage

Source service: `eks`. 40 of 70 tracked operations are implemented.

Service documentation: /aws/services/eks/
License availability: available starting with the Ultimate plan. See /aws/licensing/ for current plan details.

| Operation | Status |
| --- | --- |
| ActivateCertificateAuthority | Not implemented |
| AssociateAccessPolicy | Implemented |
| AssociateEncryptionConfig | Not implemented |
| AssociateIdentityProviderConfig | Not implemented |
| CancelUpdate | Not implemented |
| CreateAccessEntry | Implemented |
| CreateAddon | Implemented |
| CreateCapability | Not implemented |
| CreateCertificateAuthority | Not implemented |
| CreateCluster | Implemented |
| CreateEksAnywhereSubscription | Not implemented |
| CreateFargateProfile | Implemented |
| CreateNodegroup | Implemented |
| CreatePodIdentityAssociation | Implemented |
| DeleteAccessEntry | Implemented |
| DeleteAddon | Implemented |
| DeleteCapability | Not implemented |
| DeleteCertificateAuthority | Not implemented |
| DeleteCluster | Implemented |
| DeleteEksAnywhereSubscription | Not implemented |
| DeleteFargateProfile | Implemented |
| DeleteNodegroup | Implemented |
| DeletePodIdentityAssociation | Implemented |
| DeregisterCluster | Not implemented |
| DescribeAccessEntry | Implemented |
| DescribeAddon | Implemented |
| DescribeAddonConfiguration | Implemented |
| DescribeAddonVersions | Implemented |
| DescribeCapability | Not implemented |
| DescribeCertificateAuthority | Not implemented |
| DescribeCluster | Implemented |
| DescribeClusterVersions | Implemented |
| DescribeEksAnywhereSubscription | Not implemented |
| DescribeFargateProfile | Implemented |
| DescribeIdentityProviderConfig | Not implemented |
| DescribeInsight | Not implemented |
| DescribeInsightsRefresh | Not implemented |
| DescribeNodegroup | Implemented |
| DescribePodIdentityAssociation | Implemented |
| DescribeUpdate | Not implemented |
| DisassociateAccessPolicy | Implemented |
| DisassociateIdentityProviderConfig | Not implemented |
| ListAccessEntries | Implemented |
| ListAccessPolicies | Implemented |
| ListAddons | Implemented |
| ListAssociatedAccessPolicies | Implemented |
| ListCapabilities | Not implemented |
| ListCertificateAuthorities | Not implemented |
| ListClusters | Implemented |
| ListEksAnywhereSubscriptions | Not implemented |
| ListFargateProfiles | Implemented |
| ListIdentityProviderConfigs | Not implemented |
| ListInsights | Not implemented |
| ListNodegroups | Implemented |
| ListPodIdentityAssociations | Implemented |
| ListTagsForResource | Implemented |
| ListUpdates | Not implemented |
| RegisterCluster | Not implemented |
| StartInsightsRefresh | Not implemented |
| TagResource | Implemented |
| UntagResource | Implemented |
| UpdateAccessEntry | Implemented |
| UpdateAddon | Implemented |
| UpdateCapability | Not implemented |
| UpdateClusterConfig | Implemented |
| UpdateClusterVersion | Not implemented |
| UpdateEksAnywhereSubscription | Not implemented |
| UpdateNodegroupConfig | Implemented |
| UpdateNodegroupVersion | Implemented |
| UpdatePodIdentityAssociation | Implemented |
