Kaip įdiegti „Grafto“ naudojant „Pulumi“: trumpas vadovas

a-wall-of-code-qpdhrdfmow58kagoh3k9zlzu.png


Dėl klientų darbo, kai turėjau konsoliduoti jų infrastruktūrą AWS, man kilo klausimas, ar turėčiau naudoti IaC įrankį ir, jei taip, kokį? Apie sprendimo priėmimo procesą šiek tiek parašiau čia. Nuolat bandydamas atskleisti savo sprendimus ir sprendimus realiame pasaulyje, maniau, kad būtų įdomu sužinoti, kaip būtų galima priimti „Grafto“ prieglobą naudojant „Pulumi“ AWS. „Grafto“ yra mažas mano pradinio šablono projektas, kuris yra konteineriuose, todėl naudotis tokiomis paslaugomis kaip ECS ir Fargate tampa paprasta.

Tai gana naivus požiūris, kuriame neišnaudojama daug „Pulumi“ privalumų, pavyzdžiui, leidžiant mums naudoti dizaino modelius kuriant infrastruktūrą. Bet, tikėkimės, turėtų parodyti infrastruktūros naudą kaip kodą kode, kurį iš tikrųjų skaitote ir rašote kiekvieną dieną.

Kodo siena

  1package main
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6
  7	"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"
  8	"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ecs"
  9	"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/iam"
 10	"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/lb"
 11	"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/rds"
 12	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
 13)
 14
 15func main() {
 16	pulumi.Run(func(ctx *pulumi.Context) error {
 17		availabilityZones := []string{"us-east-1a", "us-east-1b"}
 18
 19		// VPC
 20		vpc, err := ec2.NewVpc(ctx, "grafto-vpc", &ec2.VpcArgs{
 21			CidrBlock:          pulumi.String("10.0.0.0/16"),
 22			EnableDnsHostnames: pulumi.Bool(true),
 23			EnableDnsSupport:   pulumi.Bool(true),
 24		})
 25		if err != nil {
 26			return err
 27		}
 28
 29		startingSubnetCidrRange := "10.0.0.0/20"
 30
 31		// SUBNETS
 32		subnets := make(map[string][]*ec2.Subnet, len(availabilityZones))
 33		for i, az := range availabilityZones {
 34			var cidrRangePublic string
 35			var cidrRangePrivate string
 36			if i == 0 {
 37				cidrRangePublic = startingSubnetCidrRange
 38				cidrRangePrivate = fmt.Sprintf("10.0.%v.0/20", 16)
 39			} else {
 40				cidrRangePublic = fmt.Sprintf("10.0.%v.0/20", 16*(i+1))
 41				cidrRangePrivate = fmt.Sprintf("10.0.%v.0/20", 16*(i+2))
 42			}
 43
 44			publicSubnet, err := ec2.NewSubnet(
 45				ctx,
 46				fmt.Sprintf("grafto-%s-subnet-%v", "public", i+1),
 47				&ec2.SubnetArgs{
 48					VpcId:            vpc.ID(),
 49					CidrBlock:        pulumi.String(cidrRangePublic),
 50					AvailabilityZone: pulumi.String(az),
 51				},
 52			)
 53			if err != nil {
 54				return err
 55			}
 56
 57			subnets["public"] = append(subnets["public"], publicSubnet)
 58
 59			privateSubnet, err := ec2.NewSubnet(
 60				ctx,
 61				fmt.Sprintf("grafto-%s-subnet-%v", "private", i+1),
 62				&ec2.SubnetArgs{
 63					VpcId:            vpc.ID(),
 64					CidrBlock:        pulumi.String(cidrRangePrivate),
 65					AvailabilityZone: pulumi.String(az),
 66				},
 67			)
 68			if err != nil {
 69				return err
 70			}
 71
 72			subnets["private"] = append(subnets["private"], privateSubnet)
 73		}
 74
 75		// INTERNET GATEWAY
 76		internetGateway, err := ec2.NewInternetGateway(
 77			ctx,
 78			"grafto-internet-gateway",
 79			&ec2.InternetGatewayArgs{
 80				VpcId: vpc.ID(),
 81			},
 82		)
 83		if err != nil {
 84			return err
 85		}
 86
 87		publicRouteTable, err := ec2.NewRouteTable(
 88			ctx,
 89			"grafto-public-route-table",
 90			&ec2.RouteTableArgs{
 91				VpcId: vpc.ID(),
 92			},
 93		)
 94		if err != nil {
 95			return err
 96		}
 97
 98		_, err = ec2.NewRoute(ctx, "grafto-public-route", &ec2.RouteArgs{
 99			DestinationCidrBlock: pulumi.String("0.0.0.0/0"),
100			GatewayId:            internetGateway.ID(),
101			RouteTableId:         publicRouteTable.ID(),
102		})
103		if err != nil {
104			return err
105		}
106
107		_, err = ec2.NewRouteTableAssociation(
108			ctx,
109			"grafto-public-route-ass-1",
110			&ec2.RouteTableAssociationArgs{
111				RouteTableId: publicRouteTable.ID(),
112				SubnetId:     subnets["public"][0].ID(),
113			},
114		)
115		if err != nil {
116			return err
117		}
118
119		_, err = ec2.NewRouteTableAssociation(
120			ctx,
121			"grafto-public-route-ass-2",
122			&ec2.RouteTableAssociationArgs{
123				RouteTableId: publicRouteTable.ID(),
124				SubnetId:     subnets["public"][1].ID(),
125			},
126		)
127		if err != nil {
128			return err
129		}
130
131		// NATGATEWAY
132		elasticIP, err := ec2.NewEip(ctx, "grafto-elastic-ip", &ec2.EipArgs{})
133		if err != nil {
134			return err
135		}
136
137		natGateway, err := ec2.NewNatGateway(ctx, "grafto-nat-gateway", &ec2.NatGatewayArgs{
138			AllocationId: elasticIP.ID(),
139			SubnetId:     subnets["public"][0].ID(),
140		})
141		if err != nil {
142			return err
143		}
144
145		privateRouteTable, err := ec2.NewRouteTable(
146			ctx,
147			"grafto-private-route-table",
148			&ec2.RouteTableArgs{
149				VpcId: vpc.ID(),
150			},
151		)
152		if err != nil {
153			return err
154		}
155
156		_, err = ec2.NewRoute(ctx, "grafto-private-route", &ec2.RouteArgs{
157			DestinationCidrBlock: pulumi.String("0.0.0.0/0"),
158			NatGatewayId:         natGateway.ID(),
159			RouteTableId:         privateRouteTable.ID(),
160		})
161		if err != nil {
162			return err
163		}
164
165		_, err = ec2.NewRouteTableAssociation(
166			ctx,
167			"grafto-private-route-ass-1",
168			&ec2.RouteTableAssociationArgs{
169				RouteTableId: privateRouteTable.ID(),
170				SubnetId:     subnets["private"][0].ID(),
171			},
172		)
173		if err != nil {
174			return err
175		}
176
177		_, err = ec2.NewRouteTableAssociation(
178			ctx,
179			"grafto-private-route-ass-2",
180			&ec2.RouteTableAssociationArgs{
181				RouteTableId: privateRouteTable.ID(),
182				SubnetId:     subnets["private"][1].ID(),
183			},
184		)
185		if err != nil {
186			return err
187		}
188
189		// SECURITY GROUP
190		applicationLoadBalancer, err := ec2.NewSecurityGroup(
191			ctx,
192			"grafto-alb-sg",
193			&ec2.SecurityGroupArgs{
194				VpcId: vpc.ID(),
195				Ingress: ec2.SecurityGroupIngressArray{
196					&ec2.SecurityGroupIngressArgs{
197						CidrBlocks: pulumi.StringArray{
198							pulumi.String("0.0.0.0/0"),
199						},
200						FromPort: pulumi.Int(80),
201						ToPort:   pulumi.Int(80),
202						Protocol: pulumi.String("tcp"),
203					},
204				},
205				Egress: ec2.SecurityGroupEgressArray{
206					&ec2.SecurityGroupEgressArgs{
207						CidrBlocks: pulumi.StringArray{
208							pulumi.String("0.0.0.0/0"),
209						},
210						FromPort: pulumi.Int(0),
211						ToPort:   pulumi.Int(0),
212						Protocol: pulumi.String("-1"),
213					},
214				},
215			},
216		)
217		if err != nil {
218			return err
219		}
220
221		ecsSG, err := ec2.NewSecurityGroup(
222			ctx,
223			"grafto-ecs-sg",
224			&ec2.SecurityGroupArgs{
225				VpcId: vpc.ID(),
226				Ingress: ec2.SecurityGroupIngressArray{
227					&ec2.SecurityGroupIngressArgs{
228						CidrBlocks: pulumi.StringArray{
229							pulumi.String("0.0.0.0/0"),
230						},
231						FromPort: pulumi.Int(0),
232						ToPort:   pulumi.Int(0),
233						Protocol: pulumi.String("-1"),
234					},
235				},
236				Egress: ec2.SecurityGroupEgressArray{
237					&ec2.SecurityGroupEgressArgs{
238						CidrBlocks: pulumi.StringArray{
239							pulumi.String("0.0.0.0/0"),
240						},
241						FromPort: pulumi.Int(0),
242						ToPort:   pulumi.Int(0),
243						Protocol: pulumi.String("-1"),
244					},
245				},
246			},
247		)
248		if err != nil {
249			return err
250		}
251
252		rdsSGG, err := ec2.NewSecurityGroup(
253			ctx,
254			"grafto-rds-sgg",
255			&ec2.SecurityGroupArgs{
256				VpcId: vpc.ID(),
257				Ingress: ec2.SecurityGroupIngressArray{
258					&ec2.SecurityGroupIngressArgs{
259						CidrBlocks: pulumi.StringArray{
260							pulumi.String("0.0.0.0/0"),
261						},
262						FromPort: pulumi.Int(0),
263						ToPort:   pulumi.Int(0),
264						Protocol: pulumi.String("-1"),
265					},
266				},
267				Egress: ec2.SecurityGroupEgressArray{
268					&ec2.SecurityGroupEgressArgs{
269						CidrBlocks: pulumi.StringArray{
270							pulumi.String("0.0.0.0/0"),
271						},
272						FromPort: pulumi.Int(0),
273						ToPort:   pulumi.Int(0),
274						Protocol: pulumi.String("-1"),
275					},
276				},
277			},
278		)
279		if err != nil {
280			return err
281		}
282
283		rdsSg, err := rds.NewSubnetGroup(ctx, "grafto-rds-sg", &rds.SubnetGroupArgs{
284			SubnetIds: pulumi.StringArray{
285				subnets["private"][0].ID(),
286				subnets["private"][1].ID(),
287			},
288		})
289		if err != nil {
290			return err
291		}
292
293		database, err := rds.NewInstance(ctx, "grafto-rds-psql", &rds.InstanceArgs{
294			AllocatedStorage:   pulumi.Int(10),
295			DbName:             pulumi.String("grafto"),
296			Password:           pulumi.String("password"),
297			Username:           pulumi.String("grafto"),
298			Engine:             pulumi.String("postgres"),
299			EngineVersion:      pulumi.String("16.3"),
300			InstanceClass:      pulumi.String("db.t3.micro"),
301			ParameterGroupName: pulumi.String("default.postgres16"),
302			DbSubnetGroupName:  rdsSg.Name,
303			VpcSecurityGroupIds: pulumi.StringArray{
304				rdsSGG.ID(),
305			},
306			SkipFinalSnapshot:  pulumi.Bool(true),
307			PubliclyAccessible: pulumi.Bool(false),
308		})
309		if err != nil {
310			return err
311		}
312
313		loadBalancer, err := lb.NewLoadBalancer(ctx, "grafto-load-balancer", &lb.LoadBalancerArgs{
314			Internal:         pulumi.Bool(false),
315			LoadBalancerType: pulumi.String("application"),
316			SecurityGroups: pulumi.StringArray{
317				applicationLoadBalancer.ID(),
318			},
319			Subnets: pulumi.StringArray{
320				subnets["public"][0].ID(),
321				subnets["public"][1].ID(),
322			},
323			EnableDeletionProtection: pulumi.Bool(false),
324		})
325		if err != nil {
326			return err
327		}
328		ctx.Export("url", pulumi.Sprintf("http://%s", loadBalancer.DnsName))
329
330		targetGroup, err := lb.NewTargetGroup(ctx, "grafto-alb-target-group", &lb.TargetGroupArgs{
331			HealthCheck: &lb.TargetGroupHealthCheckArgs{
332				Path:     pulumi.String("/api/health"),
333				Protocol: pulumi.String("HTTP"),
334			},
335			Name:       pulumi.String("grafto-app-tg"),
336			Port:       pulumi.Int(80),
337			Protocol:   pulumi.String("HTTP"),
338			TargetType: pulumi.String("ip"),
339			VpcId:      vpc.ID(),
340		})
341		if err != nil {
342			return err
343		}
344
345		_, err = lb.NewListener(ctx, "grafto-alb-listener", &lb.ListenerArgs{
346			DefaultActions: lb.ListenerDefaultActionArray{
347				lb.ListenerDefaultActionArgs{
348					TargetGroupArn: targetGroup.Arn,
349					Type:           pulumi.String("forward"),
350				},
351			},
352			LoadBalancerArn: loadBalancer.Arn,
353			Port:            pulumi.Int(80),
354			Protocol:        pulumi.String("HTTP"),
355		})
356		if err != nil {
357			return err
358		}
359
360		// IAM RELATED STUFF
361		_, err = iam.NewServiceLinkedRole(
362			ctx,
363			"elastic-container-service",
364			&iam.ServiceLinkedRoleArgs{
365				AwsServiceName: pulumi.String("ecs.amazonaws.com"),
366				Description:    pulumi.String("Role to enable Amazon ECS to manage your cluster."),
367			},
368		)
369		if err != nil {
370			return err
371		}
372
373		_, err = iam.NewServiceLinkedRole(ctx, "rds", &iam.ServiceLinkedRoleArgs{
374			AwsServiceName: pulumi.String("rds.amazonaws.com"),
375			Description:    pulumi.String("Role to enable Amazon RDS to manage your cluster."),
376		})
377		if err != nil {
378			return err
379		}
380
381		_, err = iam.NewServiceLinkedRole(ctx, "elastic-load-balancer", &iam.ServiceLinkedRoleArgs{
382			AwsServiceName: pulumi.String("elasticloadbalancing.amazonaws.com"),
383			Description:    pulumi.String("Allows ELB to call AWS services on your behalf"),
384		})
385		if err != nil {
386			return err
387		}
388
389		_, err = iam.NewServiceLinkedRole(
390			ctx,
391			"application-autoscaling",
392			&iam.ServiceLinkedRoleArgs{
393				AwsServiceName: pulumi.String("ecs.application-autoscaling.amazonaws.com"),
394				Description: pulumi.String(
395					"Allows application autoscaling to call AWS services on your behalf",
396				),
397			},
398		)
399		if err != nil {
400			return err
401		}
402
403		roleJson, err := json.Marshal(map[string]interface{}{
404			"Version": "2012-10-17",
405			"Statement": []map[string]interface{}{
406				{
407					"Action": []string{
408						"sts:AssumeRole",
409					},
410					"Principal": map[string]string{"Service": "ecs-tasks.amazonaws.com"},
411					"Effect":    "Allow",
412				},
413			},
414		})
415		if err != nil {
416			return err
417		}
418		role, err := iam.NewRole(ctx, "grafto-iam-role", &iam.RoleArgs{
419			Name:             pulumi.String("grafto-iam-role"),
420			AssumeRolePolicy: pulumi.String(string(roleJson)),
421		})
422		if err != nil {
423			return err
424		}
425
426		rolePolicyJson, err := json.Marshal(map[string]interface{}{
427			"Version": "2012-10-17",
428			"Statement": []map[string]interface{}{
429				{
430					"Action": []string{
431						"ecr:*",
432					},
433					"Effect":   "Allow",
434					"Resource": "*",
435				},
436			},
437		})
438		if err != nil {
439			return err
440		}
441		_, err = iam.NewRolePolicy(ctx, "grafto-iam-role-policy", &iam.RolePolicyArgs{
442			Name:   pulumi.String("grafto-iam-role"),
443			Role:   role.Name,
444			Policy: pulumi.String(string(rolePolicyJson)),
445		})
446		if err != nil {
447			return err
448		}
449
450		// ELASTIC CONTAINER SERVICE
451		cluster, err := ecs.NewCluster(ctx, "grafto-ecs-cluster", &ecs.ClusterArgs{
452			Name: pulumi.String("grafto"),
453		})
454		if err != nil {
455			return err
456		}
457
458		taskContainerDefinition := pulumi.JSONMarshal([]map[string]interface{}{
459			{
460				"name":  "grafto-task",
461				"image": "docker.io/mbvofdocker/grafto:pulumi-blog",
462				"portMappings": []map[string]interface{}{
463					{
464						"containerPort": 8080,
465						"hostPort":      8080,
466						"protocol":      "HTTP",
467					},
468				},
469				"essential": true,
470				"command":   []string{"./app"},
471				"environment": []map[string]interface{}{
472					{
473						"name":  "ENVIRONMENT",
474						"value": "production",
475					},
476					{
477						"name":  "SERVER_HOST",
478						"value": "0.0.0.0",
479					},
480					{
481						"name":  "SERVER_PORT",
482						"value": "8080",
483					},
484					{
485						"name":  "DEFAULT_SENDER_SIGNATURE",
486						"value": "[email protected]",
487					},
488					{
489						"name":  "POSTMARK_API_TOKEN",
490						"value": "insert-valid-token-here",
491					},
492					{
493						"name":  "DB_KIND",
494						"value": "postgres",
495					},
496					{
497						"name":  "DB_PORT",
498						"value": "5432",
499					},
500					{
501						"name": "DB_HOST",
502						"value": database.Address.ApplyT(
503							func(addr string) string {
504								return addr
505							},
506						).(pulumi.StringOutput),
507					},
508					{
509						"name": "DB_NAME",
510						"value": database.DbName.ApplyT(
511							func(name string) string {
512								return name
513							},
514						).(pulumi.StringOutput),
515					},
516					{
517						"name": "DB_USER",
518						"value": database.Username.ApplyT(
519							func(name string) string {
520								return name
521							},
522						).(pulumi.StringOutput),
523					},
524					{
525						"name": "DB_PASSWORD",
526						"value": database.Password.ApplyT(
527							func(pass *string) string {
528								return *pass
529							},
530						).(pulumi.StringOutput),
531					},
532					{
533						"name":  "DB_SSL_MODE",
534						"value": "require",
535					},
536					{
537						"name":  "PASSWORD_PEPPER",
538						"value": "lotsandlotsofrandomcharshere",
539					},
540					{
541						"name":  "PROJECT_NAME",
542						"value": "Pulumi Grafto BLog Post",
543					},
544					{
545						"name": "APP_HOST",
546						"value": loadBalancer.DnsName.ApplyT(func(url string) string {
547							return url
548						}),
549					},
550					{
551						"name":  "APP_SCHEME",
552						"value": "http",
553					},
554					{
555						"name":  "CSRF_TOKEN",
556						"value": "lotsandlotsofrandomcharshere",
557					},
558					{
559						"name":  "SESSION_KEY",
560						"value": "lotsandlotsofrandomcharshere",
561					},
562					{
563						"name":  "SESSION_ENCRYPTION_KEY",
564						"value": "lotsandlotsofrandomcharshere",
565					},
566					{
567						"name":  "TOKEN_SIGNING_KEY",
568						"value": "lotsandlotsofrandomcharshere",
569					},
570				},
571			},
572		})
573		taskDefinition, err := ecs.NewTaskDefinition(ctx, "grafto-task", &ecs.TaskDefinitionArgs{
574			ContainerDefinitions: taskContainerDefinition,
575			Cpu:                  pulumi.String("256"),
576			ExecutionRoleArn:     role.Arn,
577			Family:               pulumi.String("grafto"),
578			Memory:               pulumi.String("512"),
579			NetworkMode:          pulumi.String("awsvpc"),
580			TaskRoleArn:          role.Arn,
581		})
582		if err != nil {
583			return err
584		}
585
586		_, err = ecs.NewService(ctx, "grafto-service", &ecs.ServiceArgs{
587			Cluster:                         cluster.Arn,
588			DeploymentMaximumPercent:        pulumi.IntPtr(200),
589			DeploymentMinimumHealthyPercent: pulumi.IntPtr(50),
590			DesiredCount:                    pulumi.IntPtr(1),
591			ForceNewDeployment:              pulumi.Bool(true),
592			LoadBalancers: ecs.ServiceLoadBalancerArray{
593				&ecs.ServiceLoadBalancerArgs{
594					TargetGroupArn: targetGroup.Arn,
595					ContainerName:  pulumi.String("grafto-task"),
596					ContainerPort:  pulumi.Int(8080),
597				},
598			},
599			NetworkConfiguration: ecs.ServiceNetworkConfigurationArgs{
600				Subnets: pulumi.StringArray{
601					subnets["private"][0].ID(),
602					subnets["private"][1].ID(),
603				},
604				SecurityGroups: pulumi.StringArray{
605					ecsSG.ID(),
606				},
607			},
608			Name:            pulumi.String("grafto-ecs-service"),
609			LaunchType:      pulumi.String("FARGATE"),
610			PlatformVersion: pulumi.String("1.4.0"),
611			TaskDefinition:  taskDefinition.Arn,
612		})
613		if err != nil {
614			return err
615		}
616
617		return nil
618	})
619}

Patobulinimai

Akivaizdus pirmiau pateikto patobulinimas būtų įjungti HTTPS; jei patikrinsite apkrovos balansavimo priemonės saugos grupę, pamatysite, kad leidžiame įvesties srautą per 80 prievadą. Tai yra vienintelis įėjimo taškas, nes visos mūsų Fargate užduotys yra privačiuose tinkluose, todėl pridėjus sertifikatą jis būtų apribotas iki 443 prievado. ilgas kelias.

Pažvelkite į CIDR diapazonų skaičiavimus. Jei pridėsime per daug pasiekiamumo zonų, tai nepavyks, o tai taip pat yra kažkas, ką reikia tvarkyti. Gali būti paprastas patikrinimas, kiek AZ reikia, ir apriboti jį iki tam tikro lygio, bet vis tiek turėtų būti pataisytas.

Taip pat būtų naudinga saugoti aplinkos kintamuosius kažkur, pavyzdžiui, AWS parametrų saugykloje, o ne tiesiogiai kode.

Tikriausiai taip pat pastebėjote daugybę galimybių pakartotinai naudoti kodą per sąrankos funkcijas arba, šiuo atveju, mano asmeninį mėgstamiausią, statybininkus.

Kūrėjai gali labai supaprastinti kodą, ypač jei padidėja ECS paslaugos užduočių skaičius. Būsimame straipsnyje mes tai patobulinsime, kad galėtume lengvai išplėsti savo infrastruktūrą.

Įdomus palyginimas būtų padaryti tą patį su „Terraform“ ir pamatyti, kiek jie skiriasi, ir ar pastangos, kad infrastruktūros kodas būtų pakartotinai naudojamas naudojant skirtingus dizaino modelius, būtų prasmingos.

Bet kol kas ir viskas. Laimingas įsilaužimas!



Source link

Draugai: - Marketingo agentūra - Teisinės konsultacijos - Skaidrių skenavimas - Fotofilmų kūrimas - Miesto naujienos - Šeimos gydytojai - Saulius Narbutas - Įvaizdžio kūrimas - Veidoskaita - Nuotekų valymo įrenginiai - Teniso treniruotės - Pranešimai spaudai -