78 | ```
79 |
80 | 2. Clone the git repo to the bastion host.
81 |
82 | 3. Replace the backed configuration in the [main.tf](./main.tf) with the same S3 bucket used for the network setup and DynamoDB table in your AWS account.
83 | Add the correct backend configuration for *terraform_remote_state.network* as well.
84 |
85 | ````
86 | //Modify the bucket and dynamoDB table that are used by Terraform
87 | terraform {
88 | backend "s3" {
89 | bucket = "DOC-EXAMPLE-BUCKET"
90 | key = "private-windows-eks.tfstate"
91 | region = "eu-central-1"
92 | dynamodb_table = "private-windows-eks-tf-lock"
93 | }
94 | }
95 |
96 | data terraform_remote_state "network" {
97 | backend = "s3"
98 | config = {
99 | bucket = "DOC-EXAMPLE-BUCKET"
100 | key = "network.tfstate"
101 | region = "eu-central-1"
102 | }
103 | }
104 | ````
105 |
106 | 4. If you are using a federated role to access the AWS console, then replace the role ARN in [additional_roles_aws_auth.yaml](./yaml-templates/additional_roles_aws_auth.yaml) with the role that gets federated to allow access to the EKS cluster from the AWS console for you.
107 |
108 | 5. Deploy the EKS cluster with the following commands from the root folder of the solution:
109 |
110 | ````bash
111 | |-- private-eks-for-windows-workloads-with-terraform
112 | | |-- main.tf
113 | | |-- main-input.tfvars
114 | ````
115 |
116 | ```bash
117 | $ terraform init
118 | $ terraform apply -var-file main-input.tfvars
119 | ```
120 |
121 | 5. The Windows nodes can take a few minutes until they are successfully bootstrapped and connected to the cluster.
122 |
123 | #### Validate deployment
124 |
125 | 1. After the deployment is done, you can configure the local kubectl on the bastion host to connect to the EKS cluster.
126 |
127 | ```bash
128 | $ aws eks update-kubeconfig --name sample-cluster-01 --region eu-central-1
129 | $ kubectl get nodes
130 | ```
131 |
132 | 
133 |
134 | ## Cleanup
135 |
136 | ### EKS Cluster
137 |
138 | Execute the following inside of the root path of the repository inside the bastion host to clean-up the EKS cluster as well as the worker nodes:
139 |
140 | ````bash
141 | |-- private-eks-for-windows-workloads-with-terraform
142 | | |-- main.tf
143 | | |-- main-input.tfvars
144 | ````
145 |
146 | ```bash
147 | $ terraform destroy -var-file main-input.tfvars
148 | ```
149 |
150 | ### Bastion Host & VPCs
151 |
152 | Execute the same Terraform command again from your local workstation inside the network directory to clean-up the bastion host and both VPCs:
153 |
154 | ````bash
155 | |-- private-eks-for-windows-workloads-with-terraform
156 | | |-- network
157 | | | |-- main.tf
158 | | | |-- main-input.tfvars
159 | ````
160 |
161 | ```bash
162 | $ terraform destroy -var-file main-input.tfvars
163 | ```
164 |
165 |
166 |
167 | ## Parameters in main-input.tfvars
168 |
169 | The repository provides the following defaults for the setup:
170 |
171 | - region = "eu-central-1"
172 | - VPC
173 | - azs_private = ["eu-central-1a", "eu-central-1b", "eu-central-1c"]
174 | - private_subnets = ["10.10.1.0/24", "10.10.2.0/24", "10.10.3.0/24"]
175 | - vpc_private_cidr = "10.10.0.0/16"
176 | - vpc_public_cidr = "10.20.0.0/16"
177 | - azs_public = ["eu-central-1a"]
178 | - public_subnets = ["10.20.1.0/24"]
179 |
180 | - EKS cluster
181 | - eks_cluster_name = "sample-cluster-01"
182 | - eks_cluster_version = "1.21"
183 | - Linux nodegroup
184 | - lin_desired_size = "2"
185 | - lin_max_size = "2"
186 | - lin_min_size = "2"
187 | - lin_instance_type = "t3.medium"
188 |
189 | - Windows nodegroup
190 | - win_desired_size = "2"
191 | - win_max_size = "2"
192 | - win_min_size = "2"
193 | - win_instance_type = "t3.xlarge"
194 |
195 | ## Security
196 |
197 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
198 |
199 | ## License
200 |
201 | This library is licensed under the MIT-0 License. See the LICENSE file.
202 |
203 |
--------------------------------------------------------------------------------
/THIRD-PARTY-LICENSES:
--------------------------------------------------------------------------------
1 | The Amazon private-eks-for-windows-workloads-with-terraform Product includes the following third-party software/licensing:
2 |
3 | ** SDN Sample Scripts v.1.0 - https://github.com/microsoft/SDN
4 | Copyright (c) Microsoft Corporation
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7 |
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 |
10 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11 |
--------------------------------------------------------------------------------
/eks/cluster/main-var.tf:
--------------------------------------------------------------------------------
1 | variable "private_subnet_ids" {
2 | description = "Please enter a list of private subnet ids to be used"
3 | type = any
4 | }
5 | variable "bastion_host_SG_id" {
6 | description = "Please enter the ID of the security group of the bastion host"
7 | type = string
8 | }
9 |
10 | variable "vpc_id" {
11 | description = "Please enter the ID of the VPC"
12 | type = string
13 | }
14 | variable "region" {
15 | description = "Please enter the region used to deploy this infrastructure"
16 | type = string
17 | }
18 | variable "eks_cluster_version" {
19 | description = "Please enter the EKS cluster version"
20 | type = string
21 | }
22 | variable "eks_cluster_name" {
23 | description = "Please enter an EKS cluster name"
24 | type = string
25 | }
26 | variable "lin_instance_type" {
27 | description = "Please enter the instance type to be used for the Linux worker nodes"
28 | type = string
29 | }
30 | variable "lin_min_size" {
31 | description = "Please enter the minimal size for the Linux ASG"
32 | type = string
33 | }
34 | variable "lin_max_size" {
35 | description = "Please enter the maximal size for the Linux ASG"
36 | type = string
37 | }
38 | variable "lin_desired_size" {
39 | description = "Please enter the desired size for the Linux ASG"
40 | type = string
41 | }
42 | variable "win_min_size" {
43 | description = "Please enter the minimal size for the Windows ASG"
44 | type = string
45 | }
46 | variable "win_max_size" {
47 | description = "Please enter the maximal size for the Windows ASG"
48 | type = string
49 | }
50 | variable "win_desired_size" {
51 | description = "Please enter the desired size for the Windows ASG"
52 | type = string
53 | }
54 | variable "win_instance_type" {
55 | description = "Please enter the instance type to be used for the Windows worker nodes"
56 | type = string
57 | }
58 | variable "node_host_key_name" {
59 | description = "Please enter the name of the SSH key pair that should be assigned to the worker nodes of the cluster"
60 | type = string
61 | }
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/eks/cluster/main.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_providers {
3 | aws = {
4 | source = "hashicorp/aws"
5 | version = "3.72.0"
6 | }
7 | }
8 | }
9 | provider "aws" {
10 | region = var.region
11 | }
12 | data "aws_caller_identity" "current" {}
13 | locals {
14 | account_id = data.aws_caller_identity.current.account_id
15 | }
16 | #### Nodegroups - Images
17 |
18 | data "aws_ami" "lin_ami" {
19 | most_recent = true
20 | owners = ["amazon"]
21 | filter {
22 | name = "name"
23 | values = ["amazon-eks-node-${var.eks_cluster_version}-*"]
24 | }
25 | }
26 | data "aws_ami" "win_ami" {
27 | most_recent = true
28 | owners = ["amazon"]
29 | filter {
30 | name = "name"
31 | values = ["Windows_Server-2019-English-Core-EKS_Optimized-${var.eks_cluster_version}-*"]
32 | }
33 | }
34 | resource "aws_kms_key" "eks" {
35 | description = "EKS Encryption Key"
36 | }
37 |
38 | module "eks" {
39 | source = "terraform-aws-modules/eks/aws"
40 | version = "18.6.0"
41 | vpc_id = var.vpc_id
42 | cluster_name = var.eks_cluster_name
43 | subnet_ids = var.private_subnet_ids
44 | cluster_enabled_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"]
45 | cluster_endpoint_private_access = true
46 | cluster_endpoint_public_access = false
47 | cluster_version = var.eks_cluster_version
48 | cluster_encryption_config = [
49 | {
50 | provider_key_arn = aws_kms_key.eks.arn
51 | resources = ["secrets"]
52 | }
53 | ]
54 | ### Allow SSM access for Nodes
55 | self_managed_node_group_defaults = {
56 | iam_role_additional_policies = ["arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"]
57 | }
58 | tags = {
59 | Name = "${var.eks_cluster_name}"
60 | }
61 | # Extend node-to-node security group rules
62 | node_security_group_additional_rules = {
63 | ingress_self_all = {
64 | description = "Node to node all ports/protocols"
65 | protocol = "-1"
66 | from_port = 0
67 | to_port = 0
68 | type = "ingress"
69 | self = true
70 | }
71 | egress_all = {
72 | description = "Node all egress"
73 | protocol = "-1"
74 | from_port = 0
75 | to_port = 0
76 | type = "egress"
77 | cidr_blocks = ["0.0.0.0/0"]
78 | ipv6_cidr_blocks = ["::/0"]
79 | }
80 | ## Enable access from bastion host to Nodes
81 | ingress_bastion = {
82 | description = "Allow access from Bastion Host"
83 | type = "ingress"
84 | from_port = 443
85 | to_port = 443
86 | protocol = "tcp"
87 | source_security_group_id = var.bastion_host_SG_id
88 | }
89 | ## Enable RDP access from bastion host to Nodes
90 | ingress_bastion_win = {
91 | description = "Allow access from Bastion Host via RDP"
92 | type = "ingress"
93 | from_port = 3389
94 | to_port = 3389
95 | protocol = "tcp"
96 | source_security_group_id = var.bastion_host_SG_id
97 | }
98 | }
99 | ## Enable access from bastion host to EKS endpoint
100 | cluster_security_group_additional_rules = {
101 | ingress_bastion = {
102 | description = "Allow access from Bastion Host"
103 | type = "ingress"
104 | from_port = 443
105 | to_port = 443
106 | protocol = "tcp"
107 | source_security_group_id = var.bastion_host_SG_id
108 | }
109 | }
110 | self_managed_node_groups = {
111 | linux = {
112 | platform = "linux"
113 | name = "linux"
114 | public_ip = false
115 | instance_type = var.lin_instance_type
116 | key_name = var.node_host_key_name
117 | desired_size = var.lin_desired_size
118 | max_size = var.lin_max_size
119 | min_size = var.lin_min_size
120 | ami_id = data.aws_ami.lin_ami.id
121 | }
122 | windows = {
123 | platform = "windows"
124 | name = "windows"
125 | public_ip = false
126 | instance_type = var.win_instance_type
127 | key_name = var.node_host_key_name
128 | desired_size = var.win_desired_size
129 | max_size = var.win_max_size
130 | min_size = var.win_min_size
131 | ami_id = data.aws_ami.win_ami.id
132 | }
133 | }
134 | }
135 | ### Prerequisites for Windows Node enablement
136 | data "aws_eks_cluster_auth" "this" {
137 | name = module.eks.cluster_id
138 | }
139 |
140 | locals {
141 | kubeconfig = yamlencode({
142 | apiVersion = "v1"
143 | kind = "Config"
144 | current-context = "terraform"
145 | clusters = [{
146 | name = module.eks.cluster_id
147 | cluster = {
148 | certificate-authority-data = module.eks.cluster_certificate_authority_data
149 | server = module.eks.cluster_endpoint
150 | }
151 | }]
152 | contexts = [{
153 | name = "terraform"
154 | context = {
155 | cluster = module.eks.cluster_id
156 | user = "terraform"
157 | }
158 | }]
159 | users = [{
160 | name = "terraform"
161 | user = {
162 | token = data.aws_eks_cluster_auth.this.token
163 | }
164 | }]
165 | })
166 | }
167 | ### Apply changes to aws_auth
168 | ### Windows node Cluster enablement: https://docs.aws.amazon.com/eks/latest/userguide/windows-support.html
169 | resource "null_resource" "apply" {
170 | triggers = {
171 | kubeconfig = base64encode(local.kubeconfig)
172 | cmd_patch = <<-EOT
173 | kubectl create configmap aws-auth -n kube-system --kubeconfig <(echo $KUBECONFIG | base64 --decode)
174 | kubectl patch configmap/aws-auth --patch "${module.eks.aws_auth_configmap_yaml}" -n kube-system --kubeconfig <(echo $KUBECONFIG | base64 --decode)
175 | kubectl get cm aws-auth -n kube-system -o json --kubeconfig <(echo $KUBECONFIG | base64 --decode) | jq --arg add "`cat yaml-templates/additional_roles_aws_auth.yaml`" '.data.mapRoles += $add' | kubectl apply --kubeconfig <(echo $KUBECONFIG | base64 --decode) -f -
176 | kubectl apply --kubeconfig <(echo $KUBECONFIG | base64 --decode) -f yaml-templates/vpc-resource-controller-configmap.yaml
177 | EOT
178 | }
179 | provisioner "local-exec" {
180 | interpreter = ["/bin/bash", "-c"]
181 | environment = {
182 | KUBECONFIG = self.triggers.kubeconfig
183 | }
184 | command = self.triggers.cmd_patch
185 | }
186 | }
187 |
188 | # VPC Endpoints for private EKS cluster
189 | # https://docs.aws.amazon.com/eks/latest/userguide/private-clusters.html#vpc-endpoints-private-clusters
190 |
191 | #### Route Tables for S3 Gateway
192 | data "aws_route_table" "private-a" {
193 | subnet_id = var.private_subnet_ids[0]
194 | }
195 | data "aws_route_table" "private-b" {
196 | subnet_id = var.private_subnet_ids[1]
197 | }
198 | data "aws_route_table" "private-c" {
199 | subnet_id = var.private_subnet_ids[2]
200 | }
201 |
202 | resource "aws_vpc_endpoint" "vpce_s3_gw" {
203 | policy = jsonencode(
204 | {
205 | Statement = [
206 | {
207 | Action = "*"
208 | Effect = "Allow"
209 | Principal = "*"
210 | Resource = "*"
211 | },
212 | ]
213 | Version = "2008-10-17"
214 | }
215 | )
216 | route_table_ids = [
217 | "${data.aws_route_table.private-a.id}",
218 | "${data.aws_route_table.private-b.id}",
219 | "${data.aws_route_table.private-c.id}"
220 | ]
221 | service_name = format("com.amazonaws.${var.region}.s3")
222 | vpc_endpoint_type = "Gateway"
223 | vpc_id = var.vpc_id
224 | }
225 | resource "aws_vpc_endpoint" "vpce_ec2" {
226 | policy = jsonencode(
227 | {
228 | Statement = [
229 | {
230 | Action = "*"
231 | Effect = "Allow"
232 | Principal = "*"
233 | Resource = "*"
234 | },
235 | ]
236 | }
237 | )
238 | private_dns_enabled = true
239 |
240 | security_group_ids = [module.eks.node_security_group_id,module.eks.cluster_security_group_id]
241 | service_name = format("com.amazonaws.${var.region}.ec2")
242 | subnet_ids = var.private_subnet_ids
243 | vpc_endpoint_type = "Interface"
244 | vpc_id = var.vpc_id
245 | }
246 | resource "aws_vpc_endpoint" "vpce_logs" {
247 | policy = jsonencode(
248 | {
249 | Statement = [
250 | {
251 | Action = "*"
252 | Effect = "Allow"
253 | Principal = "*"
254 | Resource = "*"
255 | },
256 | ]
257 | }
258 | )
259 | private_dns_enabled = true
260 | security_group_ids = [module.eks.node_security_group_id,module.eks.cluster_security_group_id]
261 | service_name = format("com.amazonaws.${var.region}.logs")
262 | subnet_ids = var.private_subnet_ids
263 | vpc_endpoint_type = "Interface"
264 | vpc_id = var.vpc_id
265 | }
266 | resource "aws_vpc_endpoint" "vpce_ecrapi" {
267 | policy = jsonencode(
268 | {
269 | Statement = [
270 | {
271 | Action = "*"
272 | Effect = "Allow"
273 | Principal = "*"
274 | Resource = "*"
275 | },
276 | ]
277 | }
278 | )
279 | private_dns_enabled = true
280 | security_group_ids = [module.eks.node_security_group_id,module.eks.cluster_security_group_id]
281 | service_name = format("com.amazonaws.${var.region}.ecr.api")
282 | subnet_ids = var.private_subnet_ids
283 | vpc_endpoint_type = "Interface"
284 | vpc_id = var.vpc_id
285 | }
286 | resource "aws_vpc_endpoint" "vpce_autoscaling" {
287 | policy = jsonencode(
288 | {
289 | Statement = [
290 | {
291 | Action = "*"
292 | Effect = "Allow"
293 | Principal = "*"
294 | Resource = "*"
295 | },
296 | ]
297 | }
298 | )
299 | private_dns_enabled = true
300 | security_group_ids = [module.eks.node_security_group_id,module.eks.cluster_security_group_id]
301 | service_name = format("com.amazonaws.${var.region}.autoscaling")
302 | subnet_ids = var.private_subnet_ids
303 | vpc_endpoint_type = "Interface"
304 | vpc_id = var.vpc_id
305 |
306 | }
307 |
308 | resource "aws_vpc_endpoint" "vpce_sts" {
309 | policy = jsonencode(
310 | {
311 | Statement = [
312 | {
313 | Action = "*"
314 | Effect = "Allow"
315 | Principal = "*"
316 | Resource = "*"
317 | },
318 | ]
319 | }
320 | )
321 | private_dns_enabled = true
322 | security_group_ids = [module.eks.node_security_group_id,module.eks.cluster_security_group_id]
323 | service_name = format("com.amazonaws.${var.region}.sts")
324 | subnet_ids = var.private_subnet_ids
325 | vpc_endpoint_type = "Interface"
326 | vpc_id = var.vpc_id
327 |
328 | }
329 | resource "aws_vpc_endpoint" "vpce_elb" {
330 | policy = jsonencode(
331 | {
332 | Statement = [
333 | {
334 | Action = "*"
335 | Effect = "Allow"
336 | Principal = "*"
337 | Resource = "*"
338 | },
339 | ]
340 | }
341 | )
342 | private_dns_enabled = true
343 | security_group_ids = [module.eks.node_security_group_id,module.eks.cluster_security_group_id]
344 | service_name = format("com.amazonaws.${var.region}.elasticloadbalancing")
345 | subnet_ids = var.private_subnet_ids
346 | vpc_endpoint_type = "Interface"
347 | vpc_id = var.vpc_id
348 | }
349 | resource "aws_vpc_endpoint" "vpce_ecrdkr" {
350 | policy = jsonencode(
351 | {
352 | Statement = [
353 | {
354 | Action = "*"
355 | Effect = "Allow"
356 | Principal = "*"
357 | Resource = "*"
358 | },
359 | ]
360 | }
361 | )
362 | private_dns_enabled = true
363 | security_group_ids = [module.eks.node_security_group_id,module.eks.cluster_security_group_id]
364 | service_name = format("com.amazonaws.${var.region}.ecr.dkr")
365 | subnet_ids = var.private_subnet_ids
366 | vpc_endpoint_type = "Interface"
367 | vpc_id = var.vpc_id
368 | }
369 | ### SSM Access
370 | resource "aws_vpc_endpoint" "vpce_ec2messages" {
371 | policy = jsonencode(
372 | {
373 | Statement = [
374 | {
375 | Action = "*"
376 | Effect = "Allow"
377 | Principal = "*"
378 | Resource = "*"
379 | },
380 | ]
381 | }
382 | )
383 | private_dns_enabled = true
384 | security_group_ids = [module.eks.node_security_group_id,module.eks.cluster_security_group_id]
385 | service_name = format("com.amazonaws.${var.region}.ec2messages")
386 | subnet_ids = var.private_subnet_ids
387 | vpc_endpoint_type = "Interface"
388 | vpc_id = var.vpc_id
389 | }
390 |
391 | resource "aws_vpc_endpoint" "vpce_ssm" {
392 | policy = jsonencode(
393 | {
394 | Statement = [
395 | {
396 | Action = "*"
397 | Effect = "Allow"
398 | Principal = "*"
399 | Resource = "*"
400 | },
401 | ]
402 | }
403 | )
404 | private_dns_enabled = true
405 | security_group_ids = [module.eks.node_security_group_id,module.eks.cluster_security_group_id]
406 | service_name = format("com.amazonaws.${var.region}.ssm")
407 | subnet_ids = var.private_subnet_ids
408 | vpc_endpoint_type = "Interface"
409 | vpc_id = var.vpc_id
410 | }
411 |
412 | resource "aws_vpc_endpoint" "vpce_ssmmessages" {
413 | policy = jsonencode(
414 | {
415 | Statement = [
416 | {
417 | Action = "*"
418 | Effect = "Allow"
419 | Principal = "*"
420 | Resource = "*"
421 | },
422 | ]
423 | }
424 | )
425 | private_dns_enabled = true
426 | security_group_ids = [module.eks.node_security_group_id,module.eks.cluster_security_group_id]
427 | service_name = format("com.amazonaws.${var.region}.ssmmessages")
428 | subnet_ids = var.private_subnet_ids
429 | vpc_endpoint_type = "Interface"
430 | vpc_id = var.vpc_id
431 |
432 | }
433 |
434 |
--------------------------------------------------------------------------------
/eks/cluster/output.tf:
--------------------------------------------------------------------------------
1 | output "out_eks_cluster" {
2 | value = module.eks
3 | }
4 | output "out_eks_cluster_ca" {
5 | value = module.eks.cluster_certificate_authority_data
6 | }
7 |
--------------------------------------------------------------------------------
/example-workloads/windows/eks-sample-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: eks-sample-windows-deployment
5 | namespace: eks-sample-app
6 | labels:
7 | app: eks-sample-windows-app
8 | spec:
9 | replicas: 3
10 | selector:
11 | matchLabels:
12 | app: eks-sample-windows-app
13 | template:
14 | metadata:
15 | labels:
16 | app: eks-sample-windows-app
17 | spec:
18 | affinity:
19 | nodeAffinity:
20 | requiredDuringSchedulingIgnoredDuringExecution:
21 | nodeSelectorTerms:
22 | - matchExpressions:
23 | - key: beta.kubernetes.io/arch
24 | operator: In
25 | values:
26 | - amd64
27 | containers:
28 | - name: windows-server-iis
29 | image: mcr.microsoft.com/windows/servercore:ltsc2019
30 | ports:
31 | - name: http
32 | containerPort: 80
33 | imagePullPolicy: IfNotPresent
34 | command:
35 | - powershell.exe
36 | - -command
37 | - "<#code used from https://gist.github.com/wagnerandrade/5424431#> ; $$listener = New-Object System.Net.HttpListener ; $$listener.Prefixes.Add('http://*:80/') ; $$listener.Start() ; $$callerCounts = @{} ; Write-Host('Listening at http://*:80/') ; while ($$listener.IsListening) { ;$$context = $$listener.GetContext() ;$$requestUrl = $$context.Request.Url ;$$clientIP = $$context.Request.RemoteEndPoint.Address ;$$response = $$context.Response ;Write-Host '' ;Write-Host('> {0}' -f $$requestUrl) ; ;$$count = 1 ;$$k=$$callerCounts.Get_Item($$clientIP) ;if ($$k -ne $$null) { $$count += $$k } ;$$callerCounts.Set_Item($$clientIP, $$count) ;$$ip=(Get-NetAdapter | Get-NetIpAddress); $$header='Windows Container Web Server
' ;$$callerCountsString='' ;$$callerCounts.Keys | % { $$callerCountsString+='IP {0} callerCount {1} ' -f $$ip[1].IPAddress,$$callerCounts.Item($$_) } ;$$footer='' ;$$content='{0}{1}{2}' -f $$header,$$callerCountsString,$$footer ;Write-Output $$content ;$$buffer = [System.Text.Encoding]::UTF8.GetBytes($$content) ;$$response.ContentLength64 = $$buffer.Length ;$$response.OutputStream.Write($$buffer, 0, $$buffer.Length) ;$$response.Close() ;$$responseStatus = $$response.StatusCode ;Write-Host('< {0}' -f $$responseStatus) } ; "
38 | nodeSelector:
39 | kubernetes.io/os: windows
40 |
--------------------------------------------------------------------------------
/example-workloads/windows/eks-sample-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: eks-sample-windows-service
5 | namespace: eks-sample-app
6 | labels:
7 | app: eks-sample-windows-app
8 | spec:
9 | selector:
10 | app: eks-sample-windows-app
11 | ports:
12 | - protocol: TCP
13 | port: 80
14 | targetPort: 80
15 |
--------------------------------------------------------------------------------
/main-input.tfvars:
--------------------------------------------------------------------------------
1 | region = "eu-central-1"
2 | ### Cluster
3 | eks_cluster_name = "sample-cluster-01"
4 | eks_cluster_version = "1.21"
5 |
6 | ### Linux Nodegroup
7 | lin_desired_size = "2"
8 | lin_max_size = "2"
9 | lin_min_size = "2"
10 | lin_instance_type = "t3.medium"
11 |
12 | ### Windows Nodegroup
13 | win_desired_size = "2"
14 | win_max_size = "2"
15 | win_min_size = "2"
16 | win_instance_type = "t3.xlarge"
17 |
18 |
--------------------------------------------------------------------------------
/main-var.tf:
--------------------------------------------------------------------------------
1 | variable "region" {
2 | description = "Please enter the region used to deploy this infrastructure"
3 | type = string
4 | }
5 | variable "eks_cluster_version" {
6 | description = "Please enter the EKS cluster version"
7 | type = string
8 | }
9 | variable "eks_cluster_name" {
10 | description = "Please enter an EKS cluster name"
11 | type = string
12 | }
13 | variable "lin_instance_type" {
14 | description = "Please enter the instance type to be used for the Linux worker nodes"
15 | type = string
16 | }
17 | variable "lin_min_size" {
18 | description = "Please enter the minimal size for the Linux ASG"
19 | type = string
20 | }
21 | variable "lin_max_size" {
22 | description = "Please enter the maximal size for the Linux ASG"
23 | type = string
24 | }
25 | variable "lin_desired_size" {
26 | description = "Please enter the desired size for the Linux ASG"
27 | type = string
28 | }
29 | variable "win_min_size" {
30 | description = "Please enter the minimal size for the Windows ASG"
31 | type = string
32 | }
33 | variable "win_max_size" {
34 | description = "Please enter the maximal size for the Windows ASG"
35 | type = string
36 | }
37 | variable "win_desired_size" {
38 | description = "Please enter the desired size for the Windows ASG"
39 | type = string
40 | }
41 | variable "win_instance_type" {
42 | description = "Please enter the instance type to be used for the Windows worker nodes"
43 | type = string
44 | }
45 | variable "node_host_key_name" {
46 | description = "Please enter the name of the SSH key pair that should be assigned to the worker nodes of the cluster"
47 | type = string
48 | }
49 |
--------------------------------------------------------------------------------
/main.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_providers {
3 | aws = {
4 | source = "hashicorp/aws"
5 | version = "3.72.0"
6 | }
7 | }
8 | }
9 | provider "aws" {
10 | region = var.region
11 | }
12 |
13 | //Modify the bucket and dynamoDB table that are used by Terraform
14 | terraform {
15 | backend "s3" {
16 | bucket = "DOC-EXAMPLE-BUCKET"
17 | key = "private-windows-eks.tfstate"
18 | region = "eu-central-1"
19 | dynamodb_table = "private-windows-eks-tf-lock"
20 | }
21 | }
22 |
23 | data terraform_remote_state "network" {
24 | backend = "s3"
25 | config = {
26 | bucket = "DOC-EXAMPLE-BUCKET"
27 | key = "network.tfstate"
28 | region = "eu-central-1"
29 | }
30 | }
31 |
32 | module "cluster" {
33 | source = "./eks/cluster"
34 | region = var.region
35 | eks_cluster_name = var.eks_cluster_name
36 | eks_cluster_version = var.eks_cluster_version
37 | private_subnet_ids = data.terraform_remote_state.network.outputs.out_private_vpc.private_subnets
38 | vpc_id = data.terraform_remote_state.network.outputs.out_private_vpc.vpc_id
39 | bastion_host_SG_id = data.terraform_remote_state.network.outputs.out_bastion_host_security_group_id
40 | lin_desired_size = var.lin_desired_size
41 | lin_max_size = var.lin_max_size
42 | lin_min_size = var.lin_min_size
43 | lin_instance_type = var.lin_instance_type
44 | win_desired_size = var.win_desired_size
45 | win_max_size = var.win_max_size
46 | win_min_size = var.win_min_size
47 | win_instance_type = var.win_instance_type
48 | node_host_key_name = var.node_host_key_name
49 | }
50 |
51 |
52 |
--------------------------------------------------------------------------------
/network/bastion_host_policy.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement": [
4 | {
5 | "Effect": "Allow",
6 | "Action": [
7 | "ec2:*",
8 | "autoscaling:*",
9 | "kms:CreateKey",
10 | "ssm:ListInstanceAssociations",
11 | "ssm:UpdateInstanceInformation",
12 | "sts:GetCallerIdentity"
13 | ],
14 | "Resource": "*"
15 | },
16 | {
17 | "Effect": "Allow",
18 | "Action": [
19 | "ec2:CreateVpcEndpoint",
20 | "route53:AssociateVPCWithHostedZone"
21 | ],
22 | "Resource": "*"
23 | },
24 | {
25 | "Effect": "Allow",
26 | "Action": "ec2:RunInstances",
27 | "Resource": "*"
28 | },
29 | {
30 | "Effect": "Allow",
31 | "Action": [
32 | "iam:AddRoleToInstanceProfile",
33 | "iam:CreateInstanceProfile",
34 | "iam:DeleteInstanceProfile",
35 | "iam:GetInstanceProfile",
36 | "iam:RemoveRoleFromInstanceProfile",
37 | "iam:TagInstanceProfile",
38 | "iam:TagOpenIDConnectProvider"
39 | ],
40 | "Resource": "*"
41 | },
42 | {
43 | "Effect": "Allow",
44 | "Action": [
45 | "iam:CreateOpenIDConnectProvider",
46 | "iam:DeleteOpenIDConnectProvider",
47 | "iam:GetOpenIDConnectProvider"
48 | ],
49 | "Resource": "*"
50 | },
51 | {
52 | "Effect": "Allow",
53 | "Action": [
54 | "iam:AttachRolePolicy",
55 | "iam:CreateRole",
56 | "iam:CreateServiceLinkedRole",
57 | "iam:DeleteRole",
58 | "iam:DetachRolePolicy",
59 | "iam:GetRole",
60 | "iam:ListAttachedRolePolicies",
61 | "iam:ListInstanceProfilesForRole",
62 | "iam:ListRolePolicies",
63 | "iam:PassRole",
64 | "iam:TagRole"
65 | ],
66 | "Resource": "*"
67 | },
68 | {
69 | "Effect": "Allow",
70 | "Action": [
71 | "kms:CreateGrant",
72 | "kms:DescribeKey",
73 | "kms:GetKeyPolicy",
74 | "kms:GetKeyRotationStatus",
75 | "kms:ListResourceTags",
76 | "kms:PutKeyPolicy",
77 | "kms:ScheduleKeyDeletion",
78 | "kms:TagResource"
79 | ],
80 | "Resource": "*"
81 | },
82 | {
83 | "Effect": "Allow",
84 | "Action": [
85 | "logs:CreateLogGroup",
86 | "logs:DeleteLogGroup",
87 | "logs:DescribeLogGroups",
88 | "logs:ListTagsLogGroup",
89 | "logs:PutRetentionPolicy"
90 | ],
91 | "Resource": "*"
92 | },
93 | {
94 | "Effect": "Allow",
95 | "Action": [
96 | "eks:*"
97 | ],
98 | "Resource": "*"
99 | },
100 | {
101 | "Effect": "Allow",
102 | "Action": [
103 | "cloudwatch:*"
104 | ],
105 | "Resource": "*"
106 | },
107 | {
108 | "Effect": "Allow",
109 | "Action": [
110 | "s3:*"
111 | ],
112 | "Resource": "*"
113 | },
114 | {
115 | "Effect": "Allow",
116 | "Action": [
117 | "dynamodb:*"
118 | ],
119 | "Resource": "*"
120 | }
121 | ]
122 | }
--------------------------------------------------------------------------------
/network/main-input.tfvars:
--------------------------------------------------------------------------------
1 | region = "eu-central-1"
2 | azs_private = ["eu-central-1a", "eu-central-1b", "eu-central-1c"]
3 | private_subnets = ["10.10.1.0/24", "10.10.2.0/24", "10.10.3.0/24"]
4 | vpc_private_cidr = "10.10.0.0/16"
5 | vpc_public_cidr = "10.20.0.0/16"
6 | azs_public = ["eu-central-1a"]
7 | public_subnets = ["10.20.1.0/24"]
8 |
9 |
--------------------------------------------------------------------------------
/network/main-var.tf:
--------------------------------------------------------------------------------
1 | variable "region" {
2 | description = "Please enter the region used to deploy this infrastructure"
3 | type = string
4 | }
5 | variable "vpc_private_cidr" {
6 | description = "Please enter the CIDR for the private VPC"
7 | type = string
8 | }
9 | variable "azs_private" {
10 | description = "Please enter a list of Availability zones for the private subnets"
11 | type = any
12 | }
13 | variable "private_subnets" {
14 | description = "Please enter a list of CIDR ranges for the private subnets in the availability zones"
15 | type = any
16 | }
17 | variable "vpc_public_cidr" {
18 | description = "Please enter a CIDR for the public VPC"
19 | type = string
20 | }
21 | variable "azs_public" {
22 | description = "Please enter a list of Availability zones for the public subnets"
23 | type = any
24 | }
25 | variable "public_subnets" {
26 | description = "Please enter a list of CIDR ranges for the public subnets in the availability zones"
27 | type = any
28 | }
29 | variable "bastion_host_key_name" {
30 | description = "Please enter the name of the SSH key pair that should be assigned to the bastion host"
31 | type = string
32 | }
33 | variable "ssh_bastion_cidr" {
34 | description = "Please enter a list of CIDR range(s) that are allowed to access the Bastion Host - Usually these are your corporate CIDR ranges - You can also restrict access to only your IP address by using /32 as prefix e.g. [\"192.168.10.10/32\"] - [\"0.0.0.0/0\"] allows access from all IPv4 adresses but is not recommended"
35 | type = list(string)
36 | }
37 |
--------------------------------------------------------------------------------
/network/main.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_providers {
3 | aws = {
4 | source = "hashicorp/aws"
5 | version = "3.72.0"
6 | }
7 | }
8 | }
9 | provider "aws" {
10 | region = var.region
11 | }
12 | //Modify the bucket and dynamoDB table that are used by Terraform
13 | terraform {
14 | backend "s3" {
15 | bucket = "DOC-EXAMPLE-BUCKET"
16 | key = "network.tfstate"
17 | region = "eu-central-1"
18 | dynamodb_table = "private-windows-eks-tf-lock"
19 | }
20 | }
21 | module "private_vpc" {
22 | source = "terraform-aws-modules/vpc/aws"
23 | name = "sample-repo-vpc-private"
24 | cidr = var.vpc_private_cidr
25 | azs = var.azs_private
26 | private_subnets = var.private_subnets
27 | enable_dns_hostnames = true
28 | create_igw = false
29 | enable_nat_gateway = false
30 | enable_vpn_gateway = false
31 | }
32 | module "public_vpc" {
33 | source = "terraform-aws-modules/vpc/aws"
34 | name = "sample-repo-vpc-public"
35 | cidr = var.vpc_public_cidr
36 | create_egress_only_igw = false
37 | create_igw = true
38 | azs = var.azs_public
39 | public_subnets = var.public_subnets
40 | enable_dns_hostnames = true
41 | enable_nat_gateway = false
42 | enable_vpn_gateway = false
43 | }
44 | resource "aws_iam_instance_profile" "ec2_eks_terraform" {
45 | name = "ec2_eks_terraform"
46 | role = aws_iam_role.ec2_eks_role.name
47 | }
48 |
49 | ### Loads a pre-defined policy
50 | resource "aws_iam_policy" "ec2_eks_terraform_policy" {
51 | name = "ec2_eks_terraform_policy"
52 | path = "/"
53 | description = "Policy to create EKS cluster with Windows and Linux Nodes"
54 | policy = "${file("bastion_host_policy.json")}"
55 | }
56 | resource "aws_iam_role" "ec2_eks_role" {
57 | name = "ec2_eks_role_terraform"
58 | managed_policy_arns = [resource.aws_iam_policy.ec2_eks_terraform_policy.arn]
59 | assume_role_policy = jsonencode({
60 | Version = "2012-10-17"
61 | Statement = [
62 | {
63 | Action = "sts:AssumeRole"
64 | Effect = "Allow"
65 | Sid = ""
66 | Principal = {
67 | Service = "ec2.amazonaws.com"
68 | }
69 | },
70 | ]
71 | })
72 | }
73 |
74 | module "ec2_instance" {
75 | source = "terraform-aws-modules/ec2-instance/aws"
76 | version = "~> 3.0"
77 | name = "bastion-host"
78 | ami = data.aws_ami.amazon-linux-2.id
79 | instance_type = "t2.micro"
80 | key_name = var.bastion_host_key_name
81 | monitoring = false
82 | vpc_security_group_ids = [aws_security_group.allow_ssh.id]
83 | subnet_id = module.public_vpc.public_subnets[0]
84 | iam_instance_profile = aws_iam_instance_profile.ec2_eks_terraform.name
85 | user_data = <> ~/.bashrc
96 | kubectl version --short --client
97 | EOF
98 | }
99 | resource "aws_security_group" "allow_ssh" {
100 | name = "allow_ssh"
101 | description = "Allow SSH inbound traffic"
102 | vpc_id = module.public_vpc.vpc_id
103 |
104 | ingress {
105 | description = "SSH from VPC"
106 | from_port = 22
107 | to_port = 22
108 | protocol = "tcp"
109 | cidr_blocks = var.ssh_bastion_cidr
110 | }
111 |
112 | egress {
113 | description = "Allow egress"
114 | from_port = 0
115 | to_port = 0
116 | protocol = "-1"
117 | cidr_blocks = ["0.0.0.0/0"]
118 | }
119 | }
120 | data "aws_ami" "amazon-linux-2" {
121 | owners = ["amazon"]
122 | most_recent = true
123 |
124 | filter {
125 | name = "name"
126 | values = ["amzn2-ami-hvm-*-x86_64-ebs"]
127 | }
128 | }
129 | resource "aws_vpc_peering_connection" "bastion-private-EKS" {
130 | peer_vpc_id = module.public_vpc.vpc_id
131 | vpc_id = module.private_vpc.vpc_id
132 | auto_accept = true
133 | tags = {
134 | Name = "VPC Peering between Bastion Host and private EKS cluster"
135 | }
136 | accepter {
137 | allow_remote_vpc_dns_resolution = true
138 | }
139 |
140 | requester {
141 | allow_remote_vpc_dns_resolution = true
142 | }
143 | }
144 |
145 | resource "aws_route" "peeringConnection-private-a" {
146 | route_table_id = module.private_vpc.private_route_table_ids[0]
147 | destination_cidr_block = module.public_vpc.vpc_cidr_block
148 | vpc_peering_connection_id = aws_vpc_peering_connection.bastion-private-EKS.id
149 | }
150 | resource "aws_route" "peeringConnection-private-b" {
151 | route_table_id = module.private_vpc.private_route_table_ids[1]
152 | destination_cidr_block = module.public_vpc.vpc_cidr_block
153 | vpc_peering_connection_id = aws_vpc_peering_connection.bastion-private-EKS.id
154 | }
155 | resource "aws_route" "peeringConnection-private-c" {
156 | route_table_id = module.private_vpc.private_route_table_ids[2]
157 | destination_cidr_block = module.public_vpc.vpc_cidr_block
158 | vpc_peering_connection_id = aws_vpc_peering_connection.bastion-private-EKS.id
159 | }
160 | resource "aws_route" "peeringConnection-public-a" {
161 | route_table_id = module.public_vpc.public_route_table_ids[0]
162 | destination_cidr_block = module.private_vpc.vpc_cidr_block
163 | vpc_peering_connection_id = aws_vpc_peering_connection.bastion-private-EKS.id
164 | }
165 |
166 |
--------------------------------------------------------------------------------
/network/output.tf:
--------------------------------------------------------------------------------
1 | output "out_private_vpc" {
2 | value = module.private_vpc
3 | }
4 | output "out_public_vpc" {
5 | value = module.public_vpc
6 | }
7 | output "out_bastion_host_security_group_id" {
8 | value = aws_security_group.allow_ssh.id
9 | }
10 | output "out_private_subnets" {
11 | value = module.private_vpc.private_subnets
12 | }
13 | output "out_vpc_id" {
14 | value = module.private_vpc.vpc_id
15 | }
16 | output "out_bastion_public_ip" {
17 | value = module.ec2_instance.public_ip
18 | }
19 |
--------------------------------------------------------------------------------
/output.tf:
--------------------------------------------------------------------------------
1 | output "out_bastion_host_security_group_id" {
2 | value = data.terraform_remote_state.network.outputs.out_bastion_host_security_group_id
3 | }
4 | output "out_bastion_host_public_ip" {
5 | value = data.terraform_remote_state.network.outputs.out_bastion_public_ip
6 | }
7 | output "out_eks_cluster" {
8 | value = module.cluster.out_eks_cluster
9 | }
10 |
--------------------------------------------------------------------------------
/yaml-templates/additional_roles_aws_auth.yaml:
--------------------------------------------------------------------------------
1 | - rolearn: arn:aws:iam::111122223333:role/Admin
2 | username: admin
3 | groups:
4 | - system:masters
5 |
--------------------------------------------------------------------------------
/yaml-templates/aws-auth-cm.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: aws-auth
5 | namespace: kube-system
6 | data:
7 | mapRoles: |
8 | - rolearn: arn:aws:iam::111122223333:role/node-role
9 | username: system:node:{{EC2PrivateDNSName}}
10 | groups:
11 | - system:bootstrappers
12 | - system:nodes
13 | - rolearn: arn:aws:iam::111122223333:role/node-role
14 | username: system:node:{{EC2PrivateDNSName}}
15 | groups:
16 | - system:bootstrappers
17 | - system:nodes
18 | - eks:kube-proxy-windows
19 |
--------------------------------------------------------------------------------
/yaml-templates/vpc-resource-controller-configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: amazon-vpc-cni
5 | namespace: kube-system
6 | data:
7 | enable-windows-ipam: "true"
8 |
--------------------------------------------------------------------------------