sparse-intern-71089
11/17/2020, 6:22 PMagreeable-ram-97887
11/17/2020, 6:24 PMworried-city-86458
11/17/2020, 10:51 PMworried-city-86458
11/17/2020, 10:52 PMagreeable-ram-97887
11/18/2020, 9:19 AMworried-city-86458
11/18/2020, 7:54 PMvar cluster = new Cluster($"{prefix}-cluster",
new ClusterArgs
{
//EnabledClusterLogTypes = { "api", "audit", "authenticator", "controllerManager", "scheduler" },
RoleArn = clusterRole.Arn,
Tags = config.EnvTags,
Version = config.KubeVersion,
VpcConfig = new ClusterVpcConfigArgs { SubnetIds = subnetIds }
},
new CustomResourceOptions { DependsOn = clusterPolicies });
ClusterName = cluster.Name;
KubeConfig = cluster.GetKubeConfig();
var kubeProvider = new Provider($"{prefix}-kube", new ProviderArgs { KubeConfig = KubeConfig });
worried-city-86458
11/18/2020, 7:55 PMGetKubeConfig
is an extension method that waits for the api server via Output.Format
(see WaitForEndpoint
):
public static class ClusterExtensions
{
public static Output<string> GetKubeConfig(this Cluster cluster) =>
Output.Format(@$"{{
""apiVersion"": ""v1"",
""clusters"": [{{
""cluster"": {{
""server"": ""{cluster.WaitForEndpoint(TimeSpan.FromMinutes(5))}"",
""certificate-authority-data"": ""{cluster.CertificateAuthority.Apply(ca => ca.Data)}""
}},
""name"": ""kubernetes"",
}}],
""contexts"": [{{
""context"": {{
""cluster"": ""kubernetes"",
""user"": ""aws"",
}},
""name"": ""aws"",
}}],
""current-context"": ""aws"",
""kind"": ""Config"",
""users"": [{{
""name"": ""aws"",
""user"": {{
""exec"": {{
""apiVersion"": ""<http://client.authentication.k8s.io/v1alpha1|client.authentication.k8s.io/v1alpha1>"",
""command"": ""aws-iam-authenticator"",
""args"": [
""token"",
""-i"",
""{cluster.Name}"",
],
}},
}},
}}],
}}").Apply(Output.CreateSecret);
private static Output<string> WaitForEndpoint(this Cluster cluster, TimeSpan timeout) =>
cluster.Endpoint.Apply(async endpoint =>
{
if (!Deployment.Instance.IsDryRun)
{
await new ApiServer(endpoint).WaitForHealthzAsync(timeout);
}
return endpoint;
});
}
worried-city-86458
11/18/2020, 7:57 PMApiServer
as follows:
public class ApiServer
{
public ApiServer(string baseUrl)
{
var handler = new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator };
Client = new FlurlClient(new HttpClient(handler)) { BaseUrl = baseUrl };
HealthzPolicy = Policy.WrapAsync(
Policy.TimeoutAsync<HttpStatusCode>(context => (TimeSpan)context["Timeout"]),
Policy.HandleResult<HttpStatusCode>(status => status != HttpStatusCode.OK)
.WaitAndRetryForeverAsync(
count => TimeSpan.FromSeconds(5),
(status, count, delay) => <http://Log.Info|Log.Info>($"Waiting for api server... ({count})", ephemeral: true)));
}
public async Task<HttpStatusCode> GetHealthzAsync()
{
try
{
return (await Client.Request("healthz").AllowAnyHttpStatus().GetAsync()).ResponseMessage.StatusCode;
}
catch (Exception ex)
{
Log.Debug($"ApiServer: {ex.GetBaseException().Message}");
return HttpStatusCode.ServiceUnavailable;
}
}
public Task<HttpStatusCode> WaitForHealthzAsync(TimeSpan timeout)
=> HealthzPolicy.ExecuteAsync(context => GetHealthzAsync(), new Dictionary<string, object> { ["Timeout"] = timeout });
protected IFlurlClient Client { get; }
private AsyncPolicyWrap<HttpStatusCode> HealthzPolicy { get; }
}
... using a bunch of C# libs to auto-retry every 5s for 5mworried-city-86458
11/18/2020, 8:04 PMvar authConfigMap = new ConfigMap($"{prefix}-auth",
new ConfigMapArgs
{
Metadata = new ObjectMetaArgs
{
Namespace = "kube-system",
Name = "aws-auth"
},
Data =
{
["mapRoles"] = IamHelpers.GetRoleMappings(nodeRole, nodeRoleWin, config.AwsAccountId),
["mapUsers"] = IamHelpers.GetUserMappings()
}
},
new CustomResourceOptions { Provider = kubeProvider });
agreeable-ram-97887
11/19/2020, 11:27 AMuserarn: arn:aws:iam::111122223333:user/user.bob
username: user.bob
groups:
- system:masters
agreeable-ram-97887
11/19/2020, 11:30 AMworried-city-86458
11/19/2020, 6:40 PMpublic static Output<string> GetRoleMappings(Role nodeRole, Role nodeRoleWin, string accountId) =>
Output.Tuple<string, string, string>(nodeRole.Arn, nodeRoleWin.Arn, accountId)
.Apply(((string NodeRoleArn, string NodeRoleWinArn, string AccountId) tuple) => new[]
{
new
{
rolearn = tuple.NodeRoleArn,
username = "system:node:{{EC2PrivateDNSName}}",
groups = new[]
{
"system:bootstrappers",
"system:nodes"
}
},
new
{
rolearn = tuple.NodeRoleWinArn,
username = "system:node:{{EC2PrivateDNSName}}",
groups = new[]
{
"system:bootstrappers",
"system:nodes",
"eks:kube-proxy-windows"
}
}
}.ToYaml());
worried-city-86458
11/19/2020, 6:42 PMGetUserMappings
is similar but specifies userarn
instead of rolearn
agreeable-ram-97887
11/23/2020, 7:03 PM######################################################################
## Set-up kubernetes-auth
MAP_ROLES = iam.ec2_role.arn.apply(
lambda arn: "\n".join(
[
f"- groups:",
f" - system:bootstrappers",
f" - system:nodes",
f" rolearn: {arn}",
f" username: system:node:{{{{EC2PrivateDNSName}}}}",
]
)
)
MAP_USERS = pulumi.Output.all().apply(
lambda args: "\n".join(
[
"- userarn: arn:aws:iam::111122223333:user/bob",
" username: bob",
" groups:",
" - system:masters",
]
),
)
authConfigMap = pulumi_kubernetes.core.v1.ConfigMap(
f"{cluster_name}-auth",
metadata={"namespace": "kube-system", "name": "aws-auth"},
data={
"mapRoles": MAP_ROLES,
"mapUsers": MAP_USERS,
},
opts=pulumi.ResourceOptions(provider=k8s_provider),
)