{"id":814,"date":"2023-11-01T10:43:42","date_gmt":"2023-11-01T10:43:42","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=814"},"modified":"2023-11-01T10:43:42","modified_gmt":"2023-11-01T10:43:42","slug":"aws-cloud-controller-manager","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=814","title":{"rendered":"How to Set Up Cloud Controller Manager in AWS with Kubeadm"},"content":{"rendered":"<p>In this guide, I have added detailed steps to set up Kubernetes cloud controller manager on an AWS kubeadm cluster.<\/p>\n<p>The idea of this setup is to understand the AWS configurations involved in the Cloud Controller manager in a self-hosted kubernetes setup.<\/p>\n<p>If you want to understand how cloud controller manager works, please go through the detailed <a href=\"https:\/\/devopscube.com\/kubernetes-architecture-explained\/\">kubernetes architecture<\/a>.<\/p>\n<h2 id=\"prerequisites\">Prerequisites<\/h2>\n<p>This setup is performed in <strong>Ubuntu 22.04 Instance<\/strong> and the instance type is <strong>t2.medium<\/strong>.<\/p>\n<p>You need a minimum of two nodes for this setup. (One controller and one worker node).<\/p>\n<p>Also, ensure you have opened up all the required ports for Kubernetes on both nodes.<\/p>\n<p>Let&#8217;s get started with the setup.<\/p>\n<h2 id=\"step-1-attach-iam-roles\">Step 1: Attach IAM Roles<\/h2>\n<p>Both controller and worker nodes need IAM roles with required permissions for the cloud controller manager to interact with the AWS APIs.<\/p>\n<h3 id=\"iam-policy-for-the-controller-node\">IAM Policy for the controller node<\/h3>\n<p>Create an <a href=\"https:\/\/devopscube.com\/aws-iam-role-instance-profile\/\">IAM role<\/a> with the following permissions and attach it to the controller node<\/p>\n<pre><code>{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"autoscaling:DescribeAutoScalingGroups\",\n                \"autoscaling:DescribeLaunchConfigurations\",\n                \"autoscaling:DescribeTags\",\n                \"ec2:DescribeInstances\",\n                \"ec2:DescribeRegions\",\n                \"ec2:DescribeRouteTables\",\n                \"ec2:DescribeSecurityGroups\",\n                \"ec2:DescribeSubnets\",\n                \"ec2:DescribeVolumes\",\n                \"ec2:DescribeAvailabilityZones\",\n                \"ec2:CreateSecurityGroup\",\n                \"ec2:CreateTags\",\n                \"ec2:CreateVolume\",\n                \"ec2:ModifyInstanceAttribute\",\n                \"ec2:ModifyVolume\",\n                \"ec2:AttachVolume\",\n                \"ec2:AuthorizeSecurityGroupIngress\",\n                \"ec2:CreateRoute\",\n                \"ec2:DeleteRoute\",\n                \"ec2:DeleteSecurityGroup\",\n                \"ec2:DeleteVolume\",\n                \"ec2:DetachVolume\",\n                \"ec2:RevokeSecurityGroupIngress\",\n                \"ec2:DescribeVpcs\",\n                \"elasticloadbalancing:AddTags\",\n                \"elasticloadbalancing:AttachLoadBalancerToSubnets\",\n                \"elasticloadbalancing:ApplySecurityGroupsToLoadBalancer\",\n                \"elasticloadbalancing:CreateLoadBalancer\",\n                \"elasticloadbalancing:CreateLoadBalancerPolicy\",\n                \"elasticloadbalancing:CreateLoadBalancerListeners\",\n                \"elasticloadbalancing:ConfigureHealthCheck\",\n                \"elasticloadbalancing:DeleteLoadBalancer\",\n                \"elasticloadbalancing:DeleteLoadBalancerListeners\",\n                \"elasticloadbalancing:DescribeLoadBalancers\",\n                \"elasticloadbalancing:DescribeLoadBalancerAttributes\",\n                \"elasticloadbalancing:DetachLoadBalancerFromSubnets\",\n                \"elasticloadbalancing:DeregisterInstancesFromLoadBalancer\",\n                \"elasticloadbalancing:ModifyLoadBalancerAttributes\",\n                \"elasticloadbalancing:RegisterInstancesWithLoadBalancer\",\n                \"elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer\",\n                \"elasticloadbalancing:AddTags\",\n                \"elasticloadbalancing:CreateListener\",\n                \"elasticloadbalancing:CreateTargetGroup\",\n                \"elasticloadbalancing:DeleteListener\",\n                \"elasticloadbalancing:DeleteTargetGroup\",\n                \"elasticloadbalancing:DescribeListeners\",\n                \"elasticloadbalancing:DescribeLoadBalancerPolicies\",\n                \"elasticloadbalancing:DescribeTargetGroups\",\n                \"elasticloadbalancing:DescribeTargetHealth\",\n                \"elasticloadbalancing:ModifyListener\",\n                \"elasticloadbalancing:ModifyTargetGroup\",\n                \"elasticloadbalancing:RegisterTargets\",\n                \"elasticloadbalancing:DeregisterTargets\",\n                \"elasticloadbalancing:SetLoadBalancerPoliciesOfListener\",\n                \"iam:CreateServiceLinkedRole\",\n                \"kms:DescribeKey\"\n            ],\n            \"Resource\": [\n                \"*\"\n            ]\n        }\n    ]\n}\n<\/code><\/pre>\n<h3 id=\"iam-policy-for-the-worker-nodes\">IAM Policy for the Worker Nodes<\/h3>\n<p>Create an IAM role with the following permissions and attach it to the worker nodes.<\/p>\n<pre><code>{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"ec2:DescribeInstances\",\n                \"ec2:DescribeRegions\",\n                \"ecr:GetAuthorizationToken\",\n                \"ecr:BatchCheckLayerAvailability\",\n                \"ecr:GetDownloadUrlForLayer\",\n                \"ecr:GetRepositoryPolicy\",\n                \"ecr:DescribeRepositories\",\n                \"ecr:ListImages\",\n                \"ecr:BatchGetImage\"\n            ],\n            \"Resource\": \"*\"\n        }\n    ]\n}\n<\/code><\/pre>\n<h2 id=\"step-2-set-hostname-on-all-nodes-\">Step 2: Set Hostname (On All Nodes)<\/h2>\n<p>Ensure each node hostname has its own private DNS address. If not, change the hostname using the following command. This is a requirement.<\/p>\n<p>Run the following command on each node (controller and worker).<\/p>\n<pre><code>sudo hostnamectl set-hostname $(curl -s http:\/\/169.254.169.254\/latest\/meta-data\/local-hostname)<\/code><\/pre>\n<p>Verify the hostname by executing the hostname command.<\/p>\n<pre><code>ubuntu@node01:~$ hostname\nip-172-31-16-213.us-west-2.compute.internal<\/code><\/pre>\n<h2 id=\"step-3-install-kubeadm-system-utilities\">Step 3: Install Kubeadm System Utilities<\/h2>\n<p>Container Runtime (<strong><a href=\"https:\/\/scriptcrunch.com\/install-cri-o-ubuntu\/?ref=devopscube.com\">CRI-O<\/a><\/strong>), kubelet, Kubeadm, and kubectl are the important utilities that should be present in each node (controller and worker).<\/p>\n<p>Install utilities using a shell script <strong><code>utilities.sh<\/code><\/strong>. It installs kubeadm version <strong><code>1.30<\/code><\/strong><\/p>\n<pre><code>#!\/bin\/bash\n#\n# Common setup for all servers (Control Plane and Nodes)\n\nset -euxo pipefail\n\n# Kubernetes Variable Declaration\nKUBERNETES_VERSION=\"v1.30\"\nCRIO_VERSION=\"v1.30\"\nKUBERNETES_INSTALL_VERSION=\"1.30.0-1.1\"\n\n# Disable swap\nsudo swapoff -a\n\n# Keeps the swap off during reboot\n(crontab -l 2&gt;\/dev\/null; echo \"@reboot \/sbin\/swapoff -a\") | crontab - || true\nsudo apt-get update -y\n\n# Create the .conf file to load the modules at bootup\ncat &lt;&lt;EOF | sudo tee \/etc\/modules-load.d\/k8s.conf\noverlay\nbr_netfilter\nEOF\n\nsudo modprobe overlay\nsudo modprobe br_netfilter\n\n# Sysctl params required by setup, params persist across reboots\ncat &lt;&lt;EOF | sudo tee \/etc\/sysctl.d\/k8s.conf\nnet.bridge.bridge-nf-call-iptables  = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\nnet.ipv4.ip_forward                 = 1\nEOF\n\n# Apply sysctl params without reboot\nsudo sysctl --system\n\nsudo apt-get update -y\nsudo apt-get install -y apt-transport-https ca-certificates curl gpg\n\n# Install CRI-O Runtime\nsudo apt-get update -y\nsudo apt-get install -y software-properties-common curl apt-transport-https ca-certificates\n\ncurl -fsSL https:\/\/pkgs.k8s.io\/addons:\/cri-o:\/stable:\/$CRIO_VERSION\/deb\/Release.key |\n    gpg --dearmor -o \/etc\/apt\/keyrings\/cri-o-apt-keyring.gpg\n\necho \"deb [signed-by=\/etc\/apt\/keyrings\/cri-o-apt-keyring.gpg] https:\/\/pkgs.k8s.io\/addons:\/cri-o:\/stable:\/$CRIO_VERSION\/deb\/ \/\" |\n    tee \/etc\/apt\/sources.list.d\/cri-o.list\n\nsudo apt-get update -y\nsudo apt-get install -y cri-o\n\nsudo systemctl daemon-reload\nsudo systemctl enable crio --now\nsudo systemctl start crio.service\n\necho \"CRI runtime installed successfully\"\n\n# Install kubelet, kubectl, and kubeadm\ncurl -fsSL https:\/\/pkgs.k8s.io\/core:\/stable:\/$KUBERNETES_VERSION\/deb\/Release.key |\n    gpg --dearmor -o \/etc\/apt\/keyrings\/kubernetes-apt-keyring.gpg\n\necho \"deb [signed-by=\/etc\/apt\/keyrings\/kubernetes-apt-keyring.gpg] https:\/\/pkgs.k8s.io\/core:\/stable:\/$KUBERNETES_VERSION\/deb\/ \/\" |\n    tee \/etc\/apt\/sources.list.d\/kubernetes.list\n\nsudo apt-get update -y\nsudo apt-get install -y kubelet=\"$KUBERNETES_INSTALL_VERSION\" kubectl=\"$KUBERNETES_INSTALL_VERSION\" kubeadm=\"$KUBERNETES_INSTALL_VERSION\"\n\n# Prevent automatic updates for kubelet, kubeadm, and kubectl\nsudo apt-mark hold kubelet kubeadm kubectl\n\nsudo apt-get update -y\n\n# Install jq, a command-line JSON processor\nsudo apt-get install -y jq\n\n# Retrieve the local IP address of the eth0 interface and set it for kubelet\nlocal_ip=\"$(ip --json addr show eth1 | jq -r '.[0].addr_info[] | select(.family == \"inet\") | .local')\"\n\n# Write the local IP address to the kubelet default configuration file\ncat &gt; \/etc\/default\/kubelet &lt;&lt; EOF\nKUBELET_EXTRA_ARGS=--node-ip=$local_ip\nEOF<\/code><\/pre>\n<p>To run the script, provide execute permission.<\/p>\n<pre><code>chmod +x utilities.sh<\/code><\/pre>\n<p>Login as root and execute the script.<\/p>\n<pre><code>.\/utilities.sh<\/code><\/pre>\n<h2 id=\"step-4-initialize-kubeadm-configuration-only-in-controller-\">Step 4: Initialize Kubeadm Configuration (Only in Controller)<\/h2>\n<p>In this step, we will initialize the control plane with configurations required for the cloud controller manager.<\/p>\n<p>Create configuration file <code>kubeadm.config<\/code><\/p>\n<pre><code>apiVersion: kubeadm.k8s.io\/v1beta3\nkind: ClusterConfiguration\napiServer:\n  certSANs:\n    - 127.0.0.1\n    - 52.38.15.235\n  extraArgs:\n    bind-address: \"0.0.0.0\"\n    cloud-provider: external\nclusterName: kubernetes\nscheduler:\n  extraArgs:\n    bind-address: \"0.0.0.0\"\ncontrollerManager:\n  extraArgs:\n    bind-address: \"0.0.0.0\"\n    cloud-provider: external\nnetworking:\n  podSubnet: \"10.244.0.0\/16\"\n  serviceSubnet: \"10.96.0.0\/12\"\n---\napiVersion: kubeadm.k8s.io\/v1beta3\nkind: InitConfiguration\nnodeRegistration:\n  name: ip-172-31-21-29.us-west-2.compute.internal\n  kubeletExtraArgs:\n    cloud-provider: external\n<\/code><\/pre>\n<p>In <code>certSANs<\/code> instead of <code><strong>52.38.15.235<\/strong><\/code> choose your controller&#8217;s public IP and <strong>change the node registration name<\/strong> to your controller&#8217;s private DNS (hostname we set in step 2). Modify the pod and service subnet if required.<\/p>\n<p>Initialize the configuration to bootstrap the kubeadm configuration.<\/p>\n<pre><code>kubeadm init --config=kubeadm.config<\/code><\/pre>\n<p>At the end of the initialization, a token will be generated by the controller to join the workers. This is <strong>required to create the controller configuration<\/strong> file.<\/p>\n<p>It would like the following highlighted token. Note down the token and cert hash.<\/p>\n<pre><code>kubeadm join 172.31.21.29:6443 --token wthapw.xgfjvonbiidea4nr --discovery-token-ca-cert-hash sha256:8b2127f960d88432fb35fd7488a501ad189e1a4ab319158d0e01a1db7fec96d7\n<\/code><\/pre>\n<h2 id=\"step-5-join-the-worker-nodes-only-in-worker-nodes-\">Step 5: Join the Worker Nodes (Only in Worker Nodes)<\/h2>\n<p>Create a configuration file in each worker node <code>kubeadm-join-config.yaml<\/code>.<\/p>\n<pre><code>---\napiVersion: kubeadm.k8s.io\/v1beta3\nkind: JoinConfiguration\ndiscovery:\n  bootstrapToken:\n    token: 30am0s.hleb5xs1dyz4ridc   \n    apiServerEndpoint: \"172.31.21.29:6443\"\n    caCertHashes:\n      - \"sha256:8b2127f960d88432fb35fd7488a501ad189e1a4ab319158d0e01a1db7fec96d7\" \nnodeRegistration:\n  name: ip-172-31-18-193.us-west-2.compute.internal\n  kubeletExtraArgs:\n    cloud-provider: external\n<\/code><\/pre>\n<p>Change the <code>token<\/code> <code>apiServerEndpoint<\/code>, and <code>caCertHashes<\/code> values to the intended values that your control plane generated when it initialized. <strong><code>name<\/code><\/strong> should be the full hostname of the respective node.<\/p>\n<p>Modify the node registration name to your worker node\u2019s private DNS address.<\/p>\n<p>To join the workers to the controller, use the following command.<\/p>\n<pre><code>kubeadm join --config kubeadm-join-config.yaml<\/code><\/pre>\n<p>Modify the intended values of the configuration file and run the <code>kubeadm join<\/code> command on each worker node to join with the controller node.<\/p>\n<h2 id=\"step-6-tag-aws-resources\">Step 6: Tag AWS Resources<\/h2>\n<p>Tagging is essential to configure the Cloud Controller Manager because it ensures which AWS resources are used by which cluster.<\/p>\n<p>For example, if a cluster uses <a href=\"https:\/\/devopscube.com\/aws-load-balancers\/\">AWS Network Load Balancer<\/a> and now the cluster is being destroyed, the specified NLB also be destroyed and not affect other resources. To tag the resource, the cluster-ID is required<\/p>\n<p>To find the Cluster ID, use the following command.<\/p>\n<pre><code>kubectl config view<\/code><\/pre>\n<p>The output would be like this.<\/p>\n<pre><code>apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data: DATA+OMITTED\n    server: https:\/\/172.31.21.29:6443\n  name: kubernetes\ncontexts:\n- context:\n    cluster: kubernetes\n    user: kubernetes-admin\n  name: kubernetes-admin@kubernetes\ncurrent-context: kubernetes-admin@kubernetes\nkind: Config\npreferences: {}\nusers:\n- name: kubernetes-admin\n  user:\n    client-certificate-data: DATA+OMITTED\n    client-key-data: DATA+OMITTED\n<\/code><\/pre>\n<p>In the <code>clusters<\/code> section, the value of <code>name<\/code> is taken as the Cluster ID.<\/p>\n<p>If the AWS resources are managed by one cluster, we can provide the tag key<code> kubernetes.io\/cluster\/kubernetes<\/code> and the tag value as <code>owned<\/code> and if the resources are managed by multiple clusters, the tag key would be the same as <code>kubernetes.io\/cluster\/kubernetes<\/code> and the tag value should be mentioned as <code>shared<\/code>.<\/p>\n<blockquote><p>Tags should be added to resourecs the controller and worker node consumes, such as VPC, Subnet, EC2 instance, Security Group, etc.<\/p><\/blockquote>\n<p>Here is an example.<\/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\/03\/image-282.png\" class=\"kg-image\" alt loading=\"lazy\"><\/figure>\n<h2 id=\"step-7-configure-the-cloud-controller-manager\">Step 7: Configure the Cloud Controller Manager<\/h2>\n<p>Clone the AWS Cloud Controller repository to the controller plane node where you have the Kubectl access.<\/p>\n<pre><code>git clone https:\/\/github.com\/kubernetes\/cloud-provider-aws.git<\/code><\/pre>\n<p>Navigate to the base directory. It has all the <a href=\"https:\/\/devopscube.com\/create-kubernetes-yaml\/\">kubernetes manifests<\/a> for the cloud controller manager and the <a href=\"https:\/\/devopscube.com\/kustomize-tutorial\/\">Kustomize<\/a> file.<\/p>\n<pre><code>cd cloud-provider-aws\/examples\/existing-cluster\/base<\/code><\/pre>\n<p>Create the <a href=\"https:\/\/devopscube.com\/kubernetes-daemonset\/\">daemonset<\/a> using the following command. -k is for Kustomize.<\/p>\n<pre><code>kubectl create -k .<\/code><\/pre>\n<p>To verify the daemonset is running properly, use the following command.<\/p>\n<pre><code>kubectl get daemonset -n kube-system<\/code><\/pre>\n<p>To ensure the CCM <a href=\"https:\/\/devopscube.com\/kubernetes-pod\/\">pod<\/a> is running, use the following command.<\/p>\n<pre><code>kubectl get pods -n kube-system<\/code><\/pre>\n<h2 id=\"step-8-provision-network-load-balancer\">Step 8: Provision Network Load Balancer<\/h2>\n<p>To test if the Cloud controller manager is working, we will deploy a sample Nginx deployment and expose it over service type LoadBlancer. The cloud controller manager should be able to create a Network load balancer that routes traffic to the nginx deployment.<\/p>\n<p>Create a deployment file for the Nginx web server using the following YAML. Execute it directly.<\/p>\n<pre><code>cat &lt;&lt;EOF | kubectl apply -f -\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: nginx-deployment\nspec:\n  replicas: 3  \n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n      - name: nginx-container\n        image: nginx:latest\n        ports:\n        - containerPort: 80\nEOF<\/code><\/pre>\n<p>Create a service file to provision a load balancer <code>nginx-service-nlb.yaml<\/code>. Here the annotation <strong><code>service.beta.kubernetes.io\/aws-load-balancer-type: nlb<\/code> <\/strong> is very important. It tells the cloud controller manager to create a Network Load Balancer. By default, it deploys a classic load balancer.<\/p>\n<pre><code>cat &lt;&lt;EOF | kubectl apply -f -\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-service\n  annotations:\n    service.beta.kubernetes.io\/aws-load-balancer-type: nlb\nspec:\n  selector:\n    app: nginx\n  ports:\n    - protocol: TCP\n      port: 80\n      targetPort: 80\n  type: LoadBalancer\nEOF<\/code><\/pre>\n<p>To ensure the deployment is running properly, use the following command.<\/p>\n<pre><code>kubectl get pods<\/code><\/pre>\n<p>To ensure the service is running properly, use the following command.<\/p>\n<pre><code>kubectl get svc<\/code><\/pre>\n<p>You should see the load balancer endpoint in the output as highlighted below.<\/p>\n<pre><code>$ kubectl get svc nginx-service\nNAME            TYPE           CLUSTER-IP      EXTERNAL-IP                                                                     PORT(S)        AGE\nnginx-service   LoadBalancer   10.101.171.34   aa64c7e28c3384b5598493b6fbb04d4c-f53de39b06106733.elb.us-west-2.amazonaws.com   80:30249\/TCP   39s<\/code><\/pre>\n<p>The registration process takes a few minutes. If you check the load balancer after a while you should see the worker nodes registered as healthy targets. The service NodePort would be used as a health check port.<\/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\/03\/image-2-37.png\" class=\"kg-image\" alt=\"AWS network loadbalancer as Kubernetes service with worker nodes as registered targets.\" loading=\"lazy\"><\/figure>\n<p>Once the nodes are registered to the NLB, you should see the Nginx homepage if you visit the Load balancer URL 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\/03\/image-3-42.png\" class=\"kg-image\" alt=\"Accessing Nginx deployment over NLB URL\" loading=\"lazy\"><\/figure>\n<p>The following image shows the CCM load balancer traffic workflow to pods.<\/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\/03\/cloud-controller-aws-1.gif\" class=\"kg-image\" alt loading=\"lazy\"><\/figure>\n<h2 id=\"possible-errors-troubleshooting\">Possible Errors &amp; Troubleshooting<\/h2>\n<p>To find the logs of the CCM pod, first, you need to find the pod name and use the following command<\/p>\n<pre><code>kubectl logs -n kube-system aws-cloud-controller-manager-zwctc<\/code><\/pre>\n<p>Following are the errors I have faced in this setup<\/p>\n<h3 id=\"error-1-cloud-provider-could-not-be-initialized\"><strong>Error 1:<\/strong> Cloud provider could not be initialized<\/h3>\n<pre><code>E1028 08:39:59.394822       1 tags.go:95] Tag \"KubernetesCluster\" nor \"kubernetes.io\/cluster\/...\" not found; Kubernetes may behave unexpectedly.\nF1028 08:39:59.394858       1 main.go:106] Cloud provider could not be initialized: could not init cloud provider \"aws\": AWS cloud failed to find ClusterID\n<\/code><\/pre>\n<p><strong>Solution:<\/strong><\/p>\n<p>Ensure all resources which are used by CCM (controller and worker) should be tagged with the correct cluster-ID.<\/p>\n<p><strong>Example:<\/strong><\/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\/03\/untitled-10-1.png\" class=\"kg-image\" alt loading=\"lazy\"><\/figure>\n<h3 id=\"error-2-node-has-no-providerid\"><strong>Error 2:<\/strong> node has no providerID<\/h3>\n<pre><code>E1028 13:25:07.544115       1 node_controller.go:277] Error getting instance metadata for node addresses: error fetching node by provider ID: Invalid format for AWS instance (), and error by node name: could not look up instance ID for node \"ip-172-31-30-122\": node has no providerID\n<\/code><\/pre>\n<p><strong>solution:<\/strong><\/p>\n<p>Before starting the cluster configuration, ensure all the servers\u2019 hostname is their private DNS address also keep in mind that the configuration files also use private DNS addresses.<\/p>\n<p><strong>Example:<\/strong><\/p>\n<pre><code>root@ip-172-31-21-29:~# hostname\nip-172-31-21-29.us-west-2.compute.internal\n<\/code><\/pre>\n<h3 id=\"error-3-couldn-t-get-current-server-api-group-list\"><strong>Error 3:<\/strong> couldn&#8217;t get current server API group list<\/h3>\n<pre><code>E1030 06:04:54.829375    9140 memcache.go:265] couldn't get current server API group list: Get \"&lt;http:\/\/localhost:8080\/api?timeout=32s&gt;\": dial tcp 127.0.0.1:8080: connect: connection refused\nThe connection to the server localhost:8080 was refused - did you specify the right host or port?\n<\/code><\/pre>\n<p><strong>Solution:<\/strong><\/p>\n<p>Ensure you have a copy of <code>admin.conf<\/code> file in your home directory, if not make a copy using <code>cp<\/code> command<\/p>\n<pre><code>cp \/etc\/kubernetes\/admin.conf ~\/.kube\/config<\/code><\/pre>\n<p><strong>Example:<\/strong><\/p>\n<pre><code>root@ip-172-31-21-29:~# ls -l \/etc\/kubernetes\/ ~\/.kube\/\n\/etc\/kubernetes\/:\ntotal 36\n-rw------- 1 root root 5648 Oct 30 05:17 admin.conf\n-rw------- 1 root root 5676 Oct 30 05:48 controller-manager.conf\n-rw------- 1 root root 2112 Oct 30 05:49 kubelet.conf\ndrwxr-xr-x 3 root root 4096 Oct 30 06:17 manifests\ndrwxr-xr-x 3 root root 4096 Oct 30 05:17 pki\n-rw------- 1 root root 5624 Oct 30 05:17 scheduler.conf\n\n\/root\/.kube\/:\ntotal 12\ndrwxr-x--- 4 root root 4096 Oct 30 06:07 cache\n-rw------- 1 root root 5648 Oct 30 06:07 config\n<\/code><\/pre>\n<h3 id=\"error-4-error-execution-phase-preflight\"><strong>Error 4:<\/strong> error execution phase preflight<\/h3>\n<pre><code>[preflight] Running pre-flight checks\nerror execution phase preflight: [preflight] Some fatal errors occurred:\n        [ERROR FileAvailable--etc-kubernetes-kubelet.conf]: \/etc\/kubernetes\/kubelet.conf already exists\n        [ERROR Port-10250]: Port 10250 is in use\n        [ERROR FileAvailable--etc-kubernetes-pki-ca.crt]: \/etc\/kubernetes\/pki\/ca.crt already exists\n[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`\nTo see the stack trace of this error execute with --v=5 or higher\n<\/code><\/pre>\n<p><strong>Solution:<\/strong><\/p>\n<p>Usually, this error happens when you remove the worker node and try to join again. To resolve this issue, remove the <code>kubelet.conf<\/code> and <code>ca.crt<\/code>.<\/p>\n<pre><code>sudo rm -f \/etc\/kubernetes\/kubelet.conf\nsudo rm -f \/etc\/kubernetes\/pki\/ca.crt<\/code><\/pre>\n<p>Then join the worker node again using the following command.<\/p>\n<pre><code>kubeadm join --config kubeadm-join-config.yaml --ignore-preflight-errors=Port-10250<\/code><\/pre>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>I have done a high-level setup and tested provisioning the Network load balancer.<\/p>\n<p>Next, I will be testing storage provisioning using the cloud controller manager.<\/p>\n<p>I have added the steps after several trials and errors due to less information in the documentation. If you face any error during the setup, do drop an comment and I will look into it. To explore more, you can refer to the <a href=\"https:\/\/cloud-provider-aws.sigs.k8s.io\/?ref=devopscube.com\" rel=\"noreferrer noopener\">official documentation<\/a>.<\/p>\n<p>Also, if you are getting started with the Kubeadm cluster for <a href=\"https:\/\/devopscube.com\/cka-exam-study-guide\/\">CKA certification<\/a>, take a look at the detailed <a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/\">kubeadm setup guide<\/a>.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/aws-cloud-controller-manager\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Set Up Cloud Controller Manager in AWS with Kubeadm \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/aws-cloud-controller-manager\/<\/p>\n","protected":false},"author":1,"featured_media":815,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-814","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\/814","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=814"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/814\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/815"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=814"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=814"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=814"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}