using System.Collections.Generic; using System.Linq; using Amazon.CDK; using Amazon.CDK.AWS.EKS; using Amazon.CDK.AWS.IAM; namespace CdkShared { public static class Eks { public static string[] DefaultK8sNamespaces => new [] { "default", "kube-system", "kube-public" }; public static bool IsStandardNamespace(string namespaceName) => DefaultK8sNamespaces.Contains(namespaceName); /// /// Creates K8s namespace if namespace name is not "default", "kube-system", or "kube-public" /// /// /// /// Optional namespace labels /// public static KubernetesManifest AddNamespaceIfNecessary(this ICluster eksCluster, string namespaceName, Dictionary labels = null) { return IsStandardNamespace(namespaceName) ? null : eksCluster.AddNamespace(namespaceName, labels); } public static ServiceAccount AddServiceAccount(this ICluster eksCluster, string name, string k8sNamespace, IDependable dependency) { ServiceAccount svcAccount = eksCluster.AddServiceAccount($"svc-account-{name}-{k8sNamespace}", new ServiceAccountOptions { Name = name, Namespace = k8sNamespace }); if(dependency != null) svcAccount.Node.AddDependency(dependency); return svcAccount; } public static ServiceAccount AddServiceAccount(this ICluster eksCluster, string name, string k8sNamespace, IDependable dependency, params string[] managedIamPolicyNames) { var svcAccount = eksCluster.AddServiceAccount(name, k8sNamespace, dependency); foreach (string managedPolicyName in managedIamPolicyNames) { svcAccount.Role.AddManagedPolicy(ManagedPolicy.FromAwsManagedPolicyName(managedPolicyName)); } return svcAccount; } public static ServiceAccount AddServiceAccount(this ICluster eksCluster, string name, string k8sNamespace, IDependable dependency, params PolicyStatement[] policyStatements) { var svcAccount = eksCluster.AddServiceAccount(name, k8sNamespace, dependency); foreach (var iamPolicyStatement in policyStatements) { svcAccount.AddToPrincipalPolicy(iamPolicyStatement); } return svcAccount; } /// /// Installs AWS App Mesh Controller - an add-on integrating Kubernetes cluster with AWS App Mesh. /// /// /// K8s namespace where AWS App Mesh controller will be installed /// Set to true to enable AWS X-Ray side-car container /// public static HelmChart AddAppMeshController(this ICluster eksCluster, string appMeshControllerNamespace = "appmesh-system", bool traceWithXRayOnAppMesh = true) { // Create K8s namespace if installing the controller in non-default/kube-system namespace var amcNamespace = eksCluster.AddNamespaceIfNecessary(appMeshControllerNamespace); // Create K8s service account for the controller. This service account will be mapped to AWS IAM Roles // letting controller interface with AWS services to do its job. ServiceAccount svcAccount = eksCluster.AddServiceAccount("appmesh-controller", appMeshControllerNamespace, amcNamespace, "AWSCloudMapFullAccess", "AWSAppMeshFullAccess"); // Set Helm chart parameters var chartValues = new Dictionary { ["region"] = eksCluster.Stack.Region, ["serviceAccount"] = new Dictionary { ["create"] = false, ["name"] = svcAccount.ServiceAccountName } }; // If X-Ray tracing enabled via parameters, turn X-Ray on via helm chart params. if (traceWithXRayOnAppMesh) chartValues.Add("tracing", new Dictionary { ["enabled"] = true, ["provider"] = "x-ray" }); // Run the Helm Chart installing App Mesh controller // Note that Helm release "app-mesh-by-cdk" will be accessible using "helm list" command HelmChart chart = eksCluster.AddHelmChart("appmesh-controller", new HelmChartProps { Repository = "https://aws.github.io/eks-charts", Chart = "appmesh-controller", Release = "app-mesh-by-cdk", Namespace = appMeshControllerNamespace, Values = chartValues }); chart.Node.AddDependency(svcAccount); return chart; } /// /// Installs AWS Load Balancer Controller to let outside traffic into the cluster via AWS Application Load Balancer. /// /// /// /// public static HelmChart AddAwsLoadBalancerController(this ICluster eksCluster, string lbControllerNamespace = "kube-system") { KubernetesManifest lbcNamespace = eksCluster.AddNamespaceIfNecessary(lbControllerNamespace); ServiceAccount svcAccount = eksCluster.CreateLbControllerServiceAccount(lbControllerNamespace, lbcNamespace); // Runs Helm chart installing AWS LB controller. // The manifest will be accessible via "helm list" on the system where "cdk deploy" was run. HelmChart chart = eksCluster.AddHelmChart("aws-load-balancer-controller-chart", new HelmChartOptions { Repository = "https://aws.github.io/eks-charts", Chart = "aws-load-balancer-controller", Release = "aws-lb-controller-by-cdk", Namespace = lbControllerNamespace, Values = new Dictionary { ["fullnameOverride"] = "aws-lb-controller", ["clusterName"] = eksCluster.ClusterName, ["serviceAccount"] = new Dictionary { ["create"] = false, ["name"] = svcAccount.ServiceAccountName, }, ["vpcId"] = eksCluster.Vpc.VpcId, ["region"] = eksCluster.Stack.Region } }); chart.Node.AddDependency(svcAccount); return chart; } /// /// Creates K8s service account for AWS LB Controller to run under. /// Attaches appropriate IAM policies to the service account to let AWS LB Controller do its job. /// /// /// K8s namespace where ALB Ingress Controller will be installed /// private static ServiceAccount CreateLbControllerServiceAccount(this ICluster eksCluster, string k8sNamespace, KubernetesManifest lbcNamespace) { IEnumerable lbControllerIamPolicy = DownloadLbControllerIamPolicyStatements(); IEnumerable policies = lbControllerIamPolicy.Select(iamPolicyJsonItem => PolicyStatement.FromJson(iamPolicyJsonItem)); return eksCluster.AddServiceAccount("aws-lb-controller", k8sNamespace, lbcNamespace, policies.ToArray()); } /// /// Downloads IAM Policy JSON file for AWS LB Controller /// /// private static IEnumerable DownloadLbControllerIamPolicyStatements() { // This IAM policy is AWS-supported (https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/main/docs/install/iam_policy.json) // and is part of AWS-recommended routine of installing AWS LB Controller (https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html) const string iamPolicyUrl = "https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json"; string iamPolicyJson = Utils.ReadContentFromUrl(iamPolicyUrl); var parsedPolicy = (Dictionary)Utils.JsonToMap(iamPolicyJson); var policyStatements = (IEnumerable)parsedPolicy?["Statement"]; return policyStatements; } } }