From d610fe318d686928e6f5cd85d1e67484e9bb2c7a Mon Sep 17 00:00:00 2001 From: skudasov Date: Fri, 7 Feb 2025 11:42:48 +0100 Subject: [PATCH] infra playground --- infra/chaosmesh-playground/README.md | 24 ++ infra/chaosmesh-playground/devbox.json | 28 ++ infra/chaosmesh-playground/devbox.lock | 233 ++++++++++++ infra/chaosmesh-playground/dummy.yaml | 70 ++++ infra/chaosmesh-playground/kind.yaml | 9 + .../manifests/latency.yaml | 15 + infra/ec2/.gitignore | 21 ++ infra/ec2/README.md | 38 ++ infra/ec2/cdk.json | 77 ++++ infra/ec2/common/template.go | 346 ++++++++++++++++++ infra/ec2/ec2.go | 30 ++ infra/ec2/go.mod | 33 ++ infra/ec2/go.sum | 47 +++ 13 files changed, 971 insertions(+) create mode 100644 infra/chaosmesh-playground/README.md create mode 100644 infra/chaosmesh-playground/devbox.json create mode 100644 infra/chaosmesh-playground/devbox.lock create mode 100644 infra/chaosmesh-playground/dummy.yaml create mode 100644 infra/chaosmesh-playground/kind.yaml create mode 100644 infra/chaosmesh-playground/manifests/latency.yaml create mode 100644 infra/ec2/.gitignore create mode 100644 infra/ec2/README.md create mode 100644 infra/ec2/cdk.json create mode 100644 infra/ec2/common/template.go create mode 100644 infra/ec2/ec2.go create mode 100644 infra/ec2/go.mod create mode 100644 infra/ec2/go.sum diff --git a/infra/chaosmesh-playground/README.md b/infra/chaosmesh-playground/README.md new file mode 100644 index 0000000000..cb0d25565c --- /dev/null +++ b/infra/chaosmesh-playground/README.md @@ -0,0 +1,24 @@ +## ChaosMesh Playground + +This deployment is used to debug various [ChaosMesh](https://chaos-mesh.org/) with [Kind](https://kind.sigs.k8s.io/) + +Install [DevBox](https://www.jetify.com/devbox) and run your environment +``` +devbox run up +``` + +Overview the services +``` +devbox shell +k9s +``` +Apply experiments (inside devbox shell) +``` +kubectl apply -f manifests/latency.yaml +``` +Debug `ChaosMesh` using k9s, check daemon logs. + +Remove the environment +``` +devbox run down +``` \ No newline at end of file diff --git a/infra/chaosmesh-playground/devbox.json b/infra/chaosmesh-playground/devbox.json new file mode 100644 index 0000000000..ff8410308c --- /dev/null +++ b/infra/chaosmesh-playground/devbox.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.13.5/.schema/devbox.schema.json", + "packages": [ + "kind@0.26.0", + "kubernetes-helm@3.17.0", + "kubectl@1.32.1", + "k9s@0.32.7" + ], + "shell": { + "init_hook": [ + "echo 'Welcome to devbox!' > /dev/null" + ], + "scripts": { + "up": [ + "kind create cluster --name cm-playground --config kind.yaml", + "helm repo add chaos-mesh https://charts.chaos-mesh.org", + "helm install chaos-mesh chaos-mesh/chaos-mesh -n=chaos-mesh --set chaosDaemon.runtime=containerd --set chaosDaemon.socketPath=/run/containerd/containerd.sock --version 2.7.0", + "kubectl apply -f dummy.yaml" + ], + "down": [ + "kind delete cluster --name cm-playground" + ], + "context": [ + "kubectl config set-context" + ] + } + } +} diff --git a/infra/chaosmesh-playground/devbox.lock b/infra/chaosmesh-playground/devbox.lock new file mode 100644 index 0000000000..56906815d2 --- /dev/null +++ b/infra/chaosmesh-playground/devbox.lock @@ -0,0 +1,233 @@ +{ + "lockfile_version": "1", + "packages": { + "k9s@0.32.7": { + "last_modified": "2025-01-19T08:16:51Z", + "resolved": "github:NixOS/nixpkgs/50165c4f7eb48ce82bd063e1fb8047a0f515f8ce#k9s", + "source": "devbox-search", + "version": "0.32.7", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/davyazrjq67v3asr3c166xwh2q4q2wmj-k9s-0.32.7", + "default": true + } + ], + "store_path": "/nix/store/davyazrjq67v3asr3c166xwh2q4q2wmj-k9s-0.32.7" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/jkibg8q4phcw1yj8lx5f5avshzbjjr8f-k9s-0.32.7", + "default": true + } + ], + "store_path": "/nix/store/jkibg8q4phcw1yj8lx5f5avshzbjjr8f-k9s-0.32.7" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/3j6m04knddvzgcq3xp0vr1551s6ii93s-k9s-0.32.7", + "default": true + } + ], + "store_path": "/nix/store/3j6m04knddvzgcq3xp0vr1551s6ii93s-k9s-0.32.7" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/m8d3pcdzrcfvy3rzkjpgfr0v94a1pf4i-k9s-0.32.7", + "default": true + } + ], + "store_path": "/nix/store/m8d3pcdzrcfvy3rzkjpgfr0v94a1pf4i-k9s-0.32.7" + } + } + }, + "kind@0.26.0": { + "last_modified": "2025-01-19T08:16:51Z", + "resolved": "github:NixOS/nixpkgs/50165c4f7eb48ce82bd063e1fb8047a0f515f8ce#kind", + "source": "devbox-search", + "version": "0.26.0", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/4j12j5zijvbcyafx85a2qh8jkv4m6lnz-kind-0.26.0", + "default": true + } + ], + "store_path": "/nix/store/4j12j5zijvbcyafx85a2qh8jkv4m6lnz-kind-0.26.0" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/gkpan5sh4kmbxknrf79p9ssd1j024ydb-kind-0.26.0", + "default": true + } + ], + "store_path": "/nix/store/gkpan5sh4kmbxknrf79p9ssd1j024ydb-kind-0.26.0" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/2vsw8jyi2n4sm74sk1kbff6hq5v8nz6i-kind-0.26.0", + "default": true + } + ], + "store_path": "/nix/store/2vsw8jyi2n4sm74sk1kbff6hq5v8nz6i-kind-0.26.0" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/hp80rb9dpsk2wj5db2zngh5b8hrgwy4k-kind-0.26.0", + "default": true + } + ], + "store_path": "/nix/store/hp80rb9dpsk2wj5db2zngh5b8hrgwy4k-kind-0.26.0" + } + } + }, + "kubectl@1.32.1": { + "last_modified": "2025-01-22T06:06:04Z", + "resolved": "github:NixOS/nixpkgs/5757bbb8bd7c0630a0cc4bb19c47e588db30b97c#kubectl", + "source": "devbox-search", + "version": "1.32.1", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/18441nqn0dahiwn1rvwilqvrqwqvvknz-kubectl-1.32.1", + "default": true + }, + { + "name": "man", + "path": "/nix/store/7aqhrqy27487ph8gcqwl92lpn48zczlm-kubectl-1.32.1-man", + "default": true + }, + { + "name": "convert", + "path": "/nix/store/4ahm5rn4f326izlh8nh6zfcf066r0jp9-kubectl-1.32.1-convert" + } + ], + "store_path": "/nix/store/18441nqn0dahiwn1rvwilqvrqwqvvknz-kubectl-1.32.1" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/jylypnz6nddbqf85glbyhpmjsbgmlayj-kubectl-1.32.1", + "default": true + }, + { + "name": "man", + "path": "/nix/store/0f95qz0ynbilx8h0z350in0d8kc9aj1m-kubectl-1.32.1-man", + "default": true + }, + { + "name": "convert", + "path": "/nix/store/y9kx67c1h6sjr3l4vp7j200v640h32kr-kubectl-1.32.1-convert" + } + ], + "store_path": "/nix/store/jylypnz6nddbqf85glbyhpmjsbgmlayj-kubectl-1.32.1" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/c4ha370mr37kzmr5d3k3x2f7ln51r9fz-kubectl-1.32.1", + "default": true + }, + { + "name": "man", + "path": "/nix/store/5p29mlkb5ywfn2znwb3pxbyr187585yy-kubectl-1.32.1-man", + "default": true + }, + { + "name": "convert", + "path": "/nix/store/83qij5g1wkrrwzjn1cwnkd342ifzw97x-kubectl-1.32.1-convert" + } + ], + "store_path": "/nix/store/c4ha370mr37kzmr5d3k3x2f7ln51r9fz-kubectl-1.32.1" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/v312kkwzk8mibc9z67n53hab6mcc7cn5-kubectl-1.32.1", + "default": true + }, + { + "name": "man", + "path": "/nix/store/jb9zc5f93z67xaldz5zmmnv8d5pzc94r-kubectl-1.32.1-man", + "default": true + }, + { + "name": "convert", + "path": "/nix/store/1lkaw0m4238bichjjl5jkkpqpvxahp43-kubectl-1.32.1-convert" + } + ], + "store_path": "/nix/store/v312kkwzk8mibc9z67n53hab6mcc7cn5-kubectl-1.32.1" + } + } + }, + "kubernetes-helm@3.17.0": { + "last_modified": "2025-01-22T06:06:04Z", + "resolved": "github:NixOS/nixpkgs/5757bbb8bd7c0630a0cc4bb19c47e588db30b97c#kubernetes-helm", + "source": "devbox-search", + "version": "3.17.0", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/kqb20496bhsi6wlggidm645ncch8lzxw-kubernetes-helm-3.17.0", + "default": true + } + ], + "store_path": "/nix/store/kqb20496bhsi6wlggidm645ncch8lzxw-kubernetes-helm-3.17.0" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/d60427zv4gsskq2rsq6yfbzzwgr4xdz7-kubernetes-helm-3.17.0", + "default": true + } + ], + "store_path": "/nix/store/d60427zv4gsskq2rsq6yfbzzwgr4xdz7-kubernetes-helm-3.17.0" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/zak1xp04pcfm0b9gaap9jxp4bq0lawxq-kubernetes-helm-3.17.0", + "default": true + } + ], + "store_path": "/nix/store/zak1xp04pcfm0b9gaap9jxp4bq0lawxq-kubernetes-helm-3.17.0" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/zidsngzm5lwyabaq9fy6m9h6sifk6pg8-kubernetes-helm-3.17.0", + "default": true + } + ], + "store_path": "/nix/store/zidsngzm5lwyabaq9fy6m9h6sifk6pg8-kubernetes-helm-3.17.0" + } + } + } + } +} diff --git a/infra/chaosmesh-playground/dummy.yaml b/infra/chaosmesh-playground/dummy.yaml new file mode 100644 index 0000000000..864b111a22 --- /dev/null +++ b/infra/chaosmesh-playground/dummy.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ping-deployment +spec: + replicas: 4 + selector: + matchLabels: + app: ping-app + template: + metadata: + labels: + app: ping-app + spec: + containers: + - name: ping-container + image: alpine:latest + command: ["sh", "-c", "while true; do ping -c 1 google.com; sleep 1; done"] + +--- +apiVersion: v1 +kind: Service +metadata: + name: ping-service-1 +spec: + selector: + app: ping-app + ports: + - protocol: TCP + port: 80 + targetPort: 80 + +--- +apiVersion: v1 +kind: Service +metadata: + name: ping-service-2 +spec: + selector: + app: ping-app + ports: + - protocol: TCP + port: 80 + targetPort: 80 + +--- +apiVersion: v1 +kind: Service +metadata: + name: ping-service-3 +spec: + selector: + app: ping-app + ports: + - protocol: TCP + port: 80 + targetPort: 80 + +--- +apiVersion: v1 +kind: Service +metadata: + name: ping-service-4 +spec: + selector: + app: ping-app + ports: + - protocol: TCP + port: 80 + targetPort: 80 \ No newline at end of file diff --git a/infra/chaosmesh-playground/kind.yaml b/infra/chaosmesh-playground/kind.yaml new file mode 100644 index 0000000000..0610bb818a --- /dev/null +++ b/infra/chaosmesh-playground/kind.yaml @@ -0,0 +1,9 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + image: kindest/node:v1.27.3 + - role: worker + image: kindest/node:v1.27.3 + - role: worker + image: kindest/node:v1.27.3 \ No newline at end of file diff --git a/infra/chaosmesh-playground/manifests/latency.yaml b/infra/chaosmesh-playground/manifests/latency.yaml new file mode 100644 index 0000000000..674a8623c9 --- /dev/null +++ b/infra/chaosmesh-playground/manifests/latency.yaml @@ -0,0 +1,15 @@ +apiVersion: chaos-mesh.org/v1alpha1 +kind: NetworkChaos +metadata: + name: introduce-latency-to-google + namespace: default +spec: + action: delay + mode: all + selector: + labelSelectors: + app: ping-app + delay: + latency: "100ms" + correlation: "100" + jitter: "0ms" \ No newline at end of file diff --git a/infra/ec2/.gitignore b/infra/ec2/.gitignore new file mode 100644 index 0000000000..0c35771511 --- /dev/null +++ b/infra/ec2/.gitignore @@ -0,0 +1,21 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# go.sum should be committed +!go.sum + +# CDK asset staging directory +.cdk.staging +cdk.out +connect.sh +forward.sh diff --git a/infra/ec2/README.md b/infra/ec2/README.md new file mode 100644 index 0000000000..6d5f2433e9 --- /dev/null +++ b/infra/ec2/README.md @@ -0,0 +1,38 @@ +# Self-Serve EC2 VM Template + +You should have been authorized in AWS. + +Install [CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) and connect it to your AWS +``` +npm install -g aws-cdk +cdk bootstrap -c action=bootstrap +``` + +Deploy VM, `connect.sh` will be generated after VM is provisioned +``` +cdk deploy -c action=deploy +./connect.sh +``` + +Destroy VM +``` +cdk destroy -c action=destroy +``` +Remove `cdk-ec2-keypair.pem` manually + +## Forwarding ports +Install `chisel` on your machine +``` +curl https://i.jpillora.com/chisel! | bash +``` + +Start forwarding on the server +``` +/usr/local/bin/chisel server --port 44044 & +``` + +Copy the fingerprint and connect from your machine +``` +export CHISEL_FINGERPRINT= +./forward.sh +``` diff --git a/infra/ec2/cdk.json b/infra/ec2/cdk.json new file mode 100644 index 0000000000..c910b0d29c --- /dev/null +++ b/infra/ec2/cdk.json @@ -0,0 +1,77 @@ +{ + "app": "go mod download && go run ec2.go", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "go.mod", + "go.sum", + "**/*test.go" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true + } +} diff --git a/infra/ec2/common/template.go b/infra/ec2/common/template.go new file mode 100644 index 0000000000..40134edf6d --- /dev/null +++ b/infra/ec2/common/template.go @@ -0,0 +1,346 @@ +package common + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "github.com/aws/aws-cdk-go/awscdk/v2" + "github.com/aws/aws-cdk-go/awscdk/v2/awsec2" + "github.com/aws/aws-cdk-go/awscdk/v2/awsiam" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/jsii-runtime-go" + "github.com/google/uuid" + "github.com/rs/zerolog/log" + "golang.org/x/crypto/ssh" + "os" + "strings" + "text/template" +) + +const ( + DefaultDockerScript = `#!/bin/bash +sudo apt update -y +sudo apt upgrade -y +sudo apt install -y ca-certificates curl gnupg lsb-release +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt update -y +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin +sudo usermod -aG docker ubuntu +sudo systemctl enable docker +sudo systemctl start docker +curl https://i.jpillora.com/chisel! | bash +` + CRUNDockerScript = `#!/bin/bash +sudo apt update -y +sudo apt upgrade -y +sudo apt install -y ca-certificates curl gnupg lsb-release +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt update -y +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin +sudo usermod -aG docker ubuntu +sudo systemctl enable docker +sudo systemctl start docker + +# CRUN Docker runtime + +set -e + +# Install dependencies +sudo apt update -y +sudo apt-get install -y make git gcc build-essential pkgconf libtool \ + libsystemd-dev libprotobuf-c-dev libcap-dev libseccomp-dev libyajl-dev \ + go-md2man autoconf python3 automake + +# Clone and build crun +git clone https://github.com/containers/crun.git +cd crun +./autogen.sh +./configure +make +sudo make install + +# Configure Docker to use crun +sudo mkdir -p /etc/docker +echo '{ + "runtimes": { + "crun": { + "path": "/usr/local/bin/crun" + } + }, + "default-runtime": "crun" +}' | sudo tee /etc/docker/daemon.json + +# Restart Docker to apply changes +sudo systemctl restart docker + +# Verify crun is the default runtime +sudo docker info | grep "Default Runtime" +` +) + +// VMConfig defines the configuration for the VM +type VMConfig struct { + Region string + AMI string + Class awsec2.InstanceClass + Size awsec2.InstanceSize + UserData string + Name string + ChiselPorts []string + Tags string +} + +// NewVM creates a new EC2 instance with the given configuration +func NewVM(stackName string, config *VMConfig) error { + app := awscdk.NewApp(nil) + + action := app.Node().TryGetContext(jsii.String("action")) + if action == nil { + log.Fatal().Msg("action should be used, cdk $cmd -c action=...") + os.Exit(1) + } + a := action.(string) + log.Info().Str("Action", a).Msg("Running action") + switch a { + case "bootstrap": + return nil + case "deploy": + // Create a new stack + stack := awscdk.NewStack(app, jsii.String(stackName), nil) + + // Generate and import key pair + keyPairName, err := generateAndImportKeyPair(config) + if err != nil { + log.Fatal().Err(err).Msg("Failed to generate and import key pair") + } + + // VPC + vpc := awsec2.NewVpc(stack, jsii.String("VPC"), &awsec2.VpcProps{ + MaxAzs: jsii.Number(1), + }) + + // Hardcoded Security Group + sg := awsec2.NewSecurityGroup(stack, jsii.String("SecurityGroup"), &awsec2.SecurityGroupProps{ + Vpc: vpc, + AllowAllOutbound: jsii.Bool(true), + Description: jsii.String("Allow ports 8080 and 8082"), + }) + sg.AddIngressRule(awsec2.Peer_AnyIpv4(), awsec2.Port_Tcp(jsii.Number(22)), jsii.String("Allow SSH"), nil) + // tailscale or chisel port + sg.AddIngressRule(awsec2.Peer_AnyIpv4(), awsec2.Port_Tcp(jsii.Number(44044)), jsii.String("Allow HTTP"), nil) + + // Hardcoded EC2 Instance Role + role := awsiam.NewRole(stack, jsii.String("InstanceRole"), &awsiam.RoleProps{ + AssumedBy: awsiam.NewServicePrincipal(jsii.String("ec2.amazonaws.com"), nil), + }) + + // Parse tags + tags := parseTags(config.Tags) + + // Create EC2 instance + inst := awsec2.NewInstance(stack, jsii.String(config.Name), &awsec2.InstanceProps{ + Vpc: vpc, + InstanceType: awsec2.InstanceType_Of(config.Class, config.Size), + MachineImage: awsec2.NewGenericLinuxImage(&map[string]*string{ + config.Region: aws.String(config.AMI), + }, nil), + KeyName: jsii.String(keyPairName), + VpcSubnets: &awsec2.SubnetSelection{SubnetType: awsec2.SubnetType_PUBLIC}, + SecurityGroup: sg, + Role: role, + UserData: awsec2.UserData_Custom(jsii.String(config.UserData)), + }) + + // Add tags to the instance + for key, value := range tags { + awscdk.Tags_Of(inst).Add(jsii.String(key), jsii.String(value), nil) + } + app.Synth(nil) + if err := GenerateConnectScript(stackName, config); err != nil { + return err + } + if err := GenerateForwardScript(stackName, config); err != nil { + return err + } + case "destroy": + default: + log.Fatal().Str("Action", a).Msg("unsupported action") + } + return nil +} + +// generateAndImportKeyPair generates an RSA key pair and imports it into AWS +func generateAndImportKeyPair(config *VMConfig) (string, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return "", fmt.Errorf("failed to generate RSA key: %w", err) + } + privateKeyFile := "cdk-ec2-keypair.pem" + file, err := os.Create(privateKeyFile) + if err != nil { + return "", fmt.Errorf("failed to create key file: %w", err) + } + defer file.Close() + + _ = pem.Encode(file, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}) + _ = os.Chmod(privateKeyFile, 0400) + fmt.Printf("Private key saved to %s\n", privateKeyFile) + publicKeyBytes, err := ssh.NewPublicKey(&privateKey.PublicKey) + if err != nil { + return "", fmt.Errorf("failed to create SSH public key: %w", err) + } + sshPublicKey := string(ssh.MarshalAuthorizedKey(publicKeyBytes)) + + // import public key + sess := session.Must(session.NewSession(&aws.Config{ + Region: aws.String(config.Region), + })) + svc := ec2.New(sess) + keyPairName := fmt.Sprintf("ec2-dev-key-%s", uuid.NewString()[0:4]) + _, err = svc.ImportKeyPair(&ec2.ImportKeyPairInput{ + KeyName: aws.String(keyPairName), + PublicKeyMaterial: []byte(sshPublicKey), + }) + if err != nil { + return "", fmt.Errorf("failed to import key pair: %w", err) + } + log.Info().Str("Key", keyPairName).Msg("Key pair imported successfully") + return keyPairName, nil +} + +// parseTags parses a comma-separated string of key=value pairs into a map +func parseTags(tags string) map[string]string { + result := make(map[string]string) + pairs := strings.Split(tags, ",") + for _, pair := range pairs { + kv := strings.Split(pair, "=") + if len(kv) == 2 { + result[kv[0]] = kv[1] + } + } + return result +} + +// GenerateConnectScript generates a connect.sh script that is used to connect via SSH +func GenerateConnectScript(stackName string, config *VMConfig) error { + // Define the template for the connect.sh script + const connectTemplate = `#!/bin/bash +set -e + +# Retrieve the public IP of the instance +INSTANCE_PUBLIC_IP=$(aws ec2 describe-instances \ + --filters "Name=tag:Name,Values={{.StackName}}/{{.VMName}}" \ + --query "Reservations[*].Instances[*].PublicIpAddress" \ + --output text) + +echo "Instance Public IP: $INSTANCE_PUBLIC_IP" + +# SSH into the instance with port forwarding +ssh -o StrictHostKeyChecking=no -i cdk-ec2-keypair.pem ubuntu@$INSTANCE_PUBLIC_IP +` + + // Parse the template + tmpl, err := template.New("connect.sh").Parse(connectTemplate) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + + // Create the connect.sh file + file, err := os.Create("connect.sh") + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + defer file.Close() + + // Prepare the data for the template + data := struct { + StackName string + VMName string + }{ + StackName: stackName, + VMName: config.Name, + } + + // Execute the template and write to the file + err = tmpl.Execute(file, data) + if err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + + // Make the script executable + err = os.Chmod("connect.sh", 0755) + if err != nil { + return fmt.Errorf("failed to make script executable: %w", err) + } + + return nil +} + +// GenerateForwardScript generates a forward.sh script with optional port forwarding +func GenerateForwardScript(stackName string, config *VMConfig) error { + // Define the template for the connect.sh script + const connectTemplate = `#!/bin/bash +set -e + +# Retrieve the public IP of the instance +INSTANCE_PUBLIC_IP=$(aws ec2 describe-instances \ + --filters "Name=tag:Name,Values={{.StackName}}/{{.VMName}}" \ + --query "Reservations[*].Instances[*].PublicIpAddress" \ + --output text) + +echo "Instance Public IP: $INSTANCE_PUBLIC_IP" + +# Forward multiple ports +chisel client --fingerprint $CHISEL_FINGERPRINT $INSTANCE_PUBLIC_IP:44044 {{range .ChiselPorts}}localhost:{{.}} {{end}} +` + + // Parse the template + tmpl, err := template.New("connect.sh").Parse(connectTemplate) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + + // Create the connect.sh file + file, err := os.Create("forward.sh") + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + defer file.Close() + + // Prepare the data for the template + data := struct { + StackName string + VMName string + ChiselPorts []string + }{ + StackName: stackName, + VMName: config.Name, + ChiselPorts: config.ChiselPorts, + } + + // Execute the template and write to the file + err = tmpl.Execute(file, data) + if err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + + // Make the script executable + err = os.Chmod("forward.sh", 0755) + if err != nil { + return fmt.Errorf("failed to make script executable: %w", err) + } + + return nil +} diff --git a/infra/ec2/ec2.go b/infra/ec2/ec2.go new file mode 100644 index 0000000000..c4c15dda17 --- /dev/null +++ b/infra/ec2/ec2.go @@ -0,0 +1,30 @@ +package main + +import ( + "ec2/common" + "github.com/aws/aws-cdk-go/awscdk/v2/awsec2" + "github.com/aws/jsii-runtime-go" + "github.com/rs/zerolog/log" +) + +func main() { + defer jsii.Close() + err := common.NewVM("self-serve", &common.VMConfig{ + Name: "bcm-1", + Region: "us-east-1", + // us-east-1 latest Ubuntu + AMI: "ami-04b4f1a9cf54c11d0", + // eu-north-1 latest Ubuntu + //AMI: "ami-08eb150f611ca277f", + Class: awsec2.InstanceClass_T3, + Size: awsec2.InstanceSize_MEDIUM, + Tags: "cost-center=bcm,environment=test", + UserData: common.DefaultDockerScript, + ChiselPorts: []string{ + "8080", + }, + }) + if err != nil { + log.Fatal().Err(err).Send() + } +} diff --git a/infra/ec2/go.mod b/infra/ec2/go.mod new file mode 100644 index 0000000000..67648936e0 --- /dev/null +++ b/infra/ec2/go.mod @@ -0,0 +1,33 @@ +module ec2 + +go 1.22.0 + +toolchain go1.23.3 + +require ( + github.com/aws/aws-cdk-go/awscdk/v2 v2.173.2 + github.com/aws/aws-sdk-go v1.55.5 + github.com/aws/jsii-runtime-go v1.106.0 + github.com/google/uuid v1.6.0 + github.com/rs/zerolog v1.33.0 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 +) + +require ( + github.com/Masterminds/semver/v3 v3.3.1 // indirect + github.com/aws/constructs-go/constructs/v10 v10.4.2 // indirect + github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.215 // indirect + github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.3 // indirect + github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv6/v2 v2.1.0 // indirect + github.com/cdklabs/cloud-assembly-schema-go/awscdkcloudassemblyschema/v38 v38.0.1 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/yuin/goldmark v1.4.13 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/tools v0.28.0 // indirect +) diff --git a/infra/ec2/go.sum b/infra/ec2/go.sum new file mode 100644 index 0000000000..881affe58a --- /dev/null +++ b/infra/ec2/go.sum @@ -0,0 +1,47 @@ +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/aws/aws-cdk-go/awscdk/v2 v2.173.2/go.mod h1:rBXrKmhrluYikJ2BNzOc9Ngs2F3W5gZYiiVsKiIoBZk= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/constructs-go/constructs/v10 v10.4.2/go.mod h1:cXsNCKDV+9eR9zYYfwy6QuE4uPFp6jsq6TtH1MwBx9w= +github.com/aws/jsii-runtime-go v1.106.0/go.mod h1:HMdZwwcI8gpwetrneEa/RUkefS194IeCeh8eJQP3xSk= +github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.215/go.mod h1:vA/XAXwlZjenRmyWSRYhtYz3DlTm/UoiZpedWC/LRMU= +github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.3/go.mod h1:0xP6iiSIKPKsShb6T2kadnTq61wL+kwmHTHnEjkFZFI= +github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv6/v2 v2.1.0/go.mod h1:JY4UnvNa1YDGQ4H5wohXTHl6YVY3uCDUWl4JYUrQfb8= +github.com/cdklabs/cloud-assembly-schema-go/awscdkcloudassemblyschema/v38 v38.0.1/go.mod h1:WMWAzkRBUPWJ5Ord1ZL2KOTdqByf01PoL5EV9K9PYKQ= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=