diff --git a/content/amazon-ec2-spot-cicd-workshop/_index.md b/content/amazon-ec2-spot-cicd-workshop/_index.md
index e3b47bc5..b6a21d4d 100644
--- a/content/amazon-ec2-spot-cicd-workshop/_index.md
+++ b/content/amazon-ec2-spot-cicd-workshop/_index.md
@@ -7,27 +7,6 @@ pre: "8. "
---
## Overview
-During this workshop, you'll get hands-on with Amazon EC2 Spot and discover architectural best practices through the lens of DevOps and CI/CD. You'll deploy Jenkins build agents and test environments on Spot instances at a fraction of the cost of on-demand instances. You'll also implement mechanisms to ensure that your CI/CD tooling recovers from spot market events by decoupling application state from your compute resources. Finally, you'll migrate your CI/CD environment to a containered environment to eke out maximum performance and cost efficiency. In addition to covering the ins and outs of Spot, we'll share some of the Spot-based mechanisms used by customers to reduce the cost of their test and production workloads.
-
-## Workshop Details
-This workshop will be broken down into a series of labs that flow on from each other (that is, you must complete each lab in order before proceeding with the next). The lab exercises that will be covered are:
-
-* Workshop preparation: Deploy pre-requisite resources through Amazon CloudFormation;
-* Lab 1: Reduce the cost of builds using Amazon EC2 Spot Fleet;
-* Lab 2: Deploy testing environments using Amazon EC2 Spot, Amazon CloudFormation & Amazon EC2 Launch Templates;
-* Lab 3: Externalize state data to add resiliency and reduce cost for your CI/CD tooling;
-* Lab 4: Using containers backed by Auto Scaling Groups comprised of both on-demand and Spot instances;
-* Workshop clean up.
-
-As a reminder, you should have a laptop device (Windows/OSX/Linux are supported - tablets are not appropriate) with the current version of Google Chrome or Mozilla Firefox installed. You should also have a clean AWS account, with **AdministratorAccess** policy-level access.
-
-This workshop should take between two and three hours to complete, depending on your proficiency with the AWS services being featured.
-
-#### Additional considerations when running this workshop in a corporate IT environment
-If you are running this workshop from a corporate IT environment, contact your Systems Administrator to ensure that you will be able to establish outbound Secure Shell (SSH) connections to an Internet host:
-
-* If you cannot establish SSH connections to Internet hosts (and do not have a suitable workaround), you will not be able to complete Labs 3 & 4;
-* If you can establish SSH connections to Internet hosts, obtain from your Systems Administrator the source IP address CIDR block that connections will be established from.
-
-If you access the Internet through a transparent proxy server running in your corporate IT environment and this proxy server uses a different source address than where SSH connections come from, additional configuration of AWS Security Groups will need to be carried out. The lab guide will indicate the configuration steps required when appropriate.
-
+Amazon EC2 Spot Instances are a good fit in DevOps scenarios: to run your CI/CD pipelines (including build and test runners) and to deploy your testing environments. In this section you can select one of the two workshops for CI/CD depending on the tool that you use:
+* [Jenkins](/amazon-ec2-spot-cicd-workshop/jenkins-spot.html)
+* [GitLab](/amazon-ec2-spot-cicd-workshop/gitlab-spot.html)
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/010-prep.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/010-prep.md
new file mode 100644
index 00000000..37e8688a
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/010-prep.md
@@ -0,0 +1,63 @@
++++
+title = "Workshop Preparation"
+weight = 10
++++
+
+### Log in to AWS Cloud9 environment
+
+You can execute the steps of this workshop directly on your workstation, but then you will need to make sure that you have the command-line tools for Git, Terraform, kubectl and AWS CLI installed. Instead of that, to not change any local settings, we recommend to use [AWS Cloud9](https://aws.amazon.com/cloud9/): a cloud IDE where you can get access to the terminal and install all the required tools.
+
+A Cloud9 environment has already been provisioned for you in the CloudFormation template (created in the [**Starting the workshop**](before.html) section). You will now log in to it and do the final configuration steps: disable the AWS managed temporary credentials and download the required workshop files.
+
+1. In the AWS Console enter **Cloud9** in the search box at the top of the screen and open the service.
+2. On the **Your environments** page find the environment called like `GitLabWorkshopC9Instance-...` and click the **Open IDE** button for it.
+
+![AWS Console Screenshot: Cloud9 Environment](/images/gitlab-spot/AWSConsole-Cloud9Environment.png)
+
+3. When the environment comes up, close all tabs inside it and open a new terminal by clicking **+** > **New Terminal**.
+
+4. Open preferences tab by choosing the cogwheel icon at the top-right corner of the screen. Then choose **AWS Settings** in the navigation pane.
+5. Disable **AWS managed temporary credentials** toggle:
+
+![Cloud9 Screenshot: Preferences](/images/gitlab-spot/Cloud9-Preferences.png)
+
+6. Close the **Preferences** tab and in the terminal tab, execute the following command to verify that you are using the correct role (in the output you should see `GitLabWorkshopC9Role` and green `OK`):
+```
+aws sts get-caller-identity --query Arn | grep GitLabWorkshopC9Role && echo -e "\033[0;32mOK\033[0m" || echo -e "\033[0;31mNOT OK\033[0m"
+```
+
+![Cloud9 Screenshot: Caller identity](/images/gitlab-spot/Cloud9-CallerIdentity.png)
+
+7. During the workshop, you will need to refer to the identifiers of the resources created by the CloudFormation stack you deployed. To reduce copy and paste across the CloudFormation console and the Cloud9 environment, you will load the CloudFormation Stack Outputs to environment variables. Configure the stack_name environment variable with the name of your CloudFormation template (substitute the `mod-gitlab-spot-workshop` with the actual stack name that you created / identified in the [Starting the workshop](before.html) section):
+```
+export stack_name=mod-gitlab-spot-workshop
+```
+
+8. Now, load the CloudFormation stack outputs on environment variables running the following commands:
+```
+export AWS_REGION=$(curl --silent http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)
+for output in $(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[].Outputs[].OutputKey' --output text)
+do
+ export $output=$(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[].Outputs[?OutputKey==`'$output'`].OutputValue' --output text) >> ~/.bash_profile
+ eval "echo $output : \"\$$output\""
+
+ eval "echo export $output=\"\$$output\"" >> ~/.bash_profile
+done
+. ~/.bash_profile
+```
+
+If successful, the output should be similar to the following:
+```
+Subnet1 : subnet-0225d837e8401e3a7
+VPC : vpc-0cef9a85fe90459e6
+GitLabPassword : 5HfKdkUxc880EDnV
+GitLabURL : https://d1in48s0v2ygmm.cloudfront.net
+GitLabCacheBucket : mod-gitlab-spot-workshop-gitlabcachebucket-aoggbfq6go76
+Subnet1Zone : us-east-1a
+```
+
+9. Finally, enable Cloud9 to show hidden files (you will need it to modify the scripts of GitLab CI/CD later). To do this choose the small cogwheel icon right above the file tree and choose **Show Hidden Files** if it has not been enabled yet (if there is a tick to the left of it, do not click again, as it will disable the display of hidden files):
+
+![Cloud9 Screenshot: Show hidden files](/images/gitlab-spot/Cloud9-ShowHiddenFiles.png)
+
+You are now ready to start the main sections of the workshop! Please proceed to [**Create a GitLab repository**](020-create-gitlab-repo.html).
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/020-create-gitlab-repo.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/020-create-gitlab-repo.md
new file mode 100644
index 00000000..7d342420
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/020-create-gitlab-repo.md
@@ -0,0 +1,75 @@
++++
+title = "Create a GitLab repository"
+weight = 20
++++
+In this lab, you will create a new repository in GitLab and configure it in AWS Cloud9. Next, you will create the source code of the demo app and commit it into the repository.
+
+### Log in to GitLab
+
+1. Run below command to get GitLab URL and password from saved environment variables:
+```
+echo GitLabURL = $GitLabURL
+echo GitLabPassword = $GitLabPassword
+```
+2. Open a new browser tab and proceed to GitLab URL.
+3. Log in to GitLab with username `root` and the password from **GitLabPassword** output value.
+
+### Create a new repository
+**Challenge:** If you are familiar with GitLab, then test your knowledge by creating a new empty private repository called `GitLab Spot Workshop`.
+
+{{%expand "Click to reveal the instructions" %}}
+1. Choose **New project** on the **Projects** page.
+2. Choose **Create blank project**.
+3. In the **Project name** field type `GitLab Spot Workshop`, in the dropdown next to **Project URL** select `root` user, and unmark the checkbox **Initialize repository with a README**. Leave the rest values as default, and choose **Create project**:
+
+![GitLab Screenshot: Create blank project](/images/gitlab-spot/GitLab-CreateBlankProject.png)
+
+4. Copy the repository URL (in format `https://xxx.cloudfront.net/root/gitlab-spot-workshop.git`) that is displayed, for example, in **Create a new repository** section on the screen.
+
+{{% /expand%}}
+
+### Add GitLab as origin to the demo app repository
+
+You will now initialize Git repository in the demo app and add your GitLab environment as an origin to it.
+
+1. Return to Cloud9 tab and execute the following command to switch to the directory with the demo application:
+
+```
+cd ~/environment/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/
+```
+
+2. Execute the following command to substitute the `ECR_ADDRESS` and other placeholders with their actual values (like the address of ECR repository created in the CloudFormation stack) and save it into `.gitlab-ci.yml` file as expected by GitLab CI/CD:
+
+```
+TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 600")
+REGION=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region)
+export ECR_INFO=$(aws ecr describe-repositories --region $REGION --query "repositories[?contains(repositoryName, 'gitlab-spot-demo')].repositoryUri" --output text)
+export ECR_ADDRESS=$(echo $ECR_INFO | awk -F'/' '{print $1}')
+export ECR_NAME=$(echo $ECR_INFO | awk -F'/' '{print $2}')
+sed "s/\${ECR_ADDRESS}/${ECR_ADDRESS}/g" template-gitlab-ci.yml | sed "s/\${ECR_NAME}/${ECR_NAME}/g" | sed "s/\${AWS_REGION}/${REGION}/g" > .gitlab-ci.yml
+```
+
+3. In the file tree on the left open file `amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/.gitlab-ci.yml` (if you don't see it, make sure you have enabled the hidden files in [**Workshop Preparation**](010-prep.html)). Look through it to understand what it does.
+4. Return to the terminal tab and execute the following commands to define your name and email that will be used in Git (replace `Your Name` and `youremail@test.tld` with the values you prefer):
+
+```
+git config --global user.name "Your Name"
+git config --global user.email "youremail@test.tld"
+```
+
+5. Optionally you can enable the storage of the GitLab login and password in Git configuration, otherwise you will need to enter them each time you work with the repository:
+
+```
+git config --global credential.helper store
+```
+
+6. Initialize Git inside the directory (use the actual URL of GitLab repositoy that you saved in the previous section) and do your first commit:
+
+```
+git init --initial-branch=main
+git remote add origin https://xxx.cloudfront.net/root/gitlab-spot-workshop.git
+git add .
+git commit -m "Initial commit"
+```
+
+You are now ready to do the key step in configuring GitLab CI/CD on Spot instances: add the runners. Please proceed to [**Configure GitLab runners on Spot instances**](030-configure-gitlab-runners-on-spot.html).
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/030-configure-gitlab-runners-on-spot.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/030-configure-gitlab-runners-on-spot.md
new file mode 100644
index 00000000..d2554330
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/030-configure-gitlab-runners-on-spot.md
@@ -0,0 +1,229 @@
++++
+title = "Configure GitLab runners on Spot instances"
+chapter = false
+weight = 30
++++
+
+In this lab you will configure GitLab CI/CD runners using **GitLab HA Scaling Runner Vending Machine for AWS** solution. You can find more about its features [**here**](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/FEATURES.md). It is built using Infrastructure as Code (IaC) with AWS CloudFormation, but you can implement similar logic using any IaC solution of your choice.
+
+There are also other ways to create GitLab runners on spot instances that we are not reviewing in this workshop: using Docker Machine, runners inside containers in a Kubernetes cluster with the worker nodes on spot instances, or in Amazon ECS with Fargate Spot.
+
+### Create an IAM role for GitLab Runners
+
+1. Create the trust policy document named `ec2-role-trust-policy.json`.
+```
+cat << EOF > ~/environment/ec2-role-trust-policy.json
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": { "Service": "ec2.amazonaws.com"},
+ "Action": "sts:AssumeRole"
+ }
+ ]
+}
+EOF
+```
+
+2. Create the **gitlab-runner** role and specify the trust policy that you created using the `create-role` command.
+```
+aws iam create-role \
+ --role-name gitlab-runner \
+ --assume-role-policy-document file://~/environment/ec2-role-trust-policy.json
+```
+
+3. Create **gitlab-runner-policy.json** access policy document that grants access to Amazon EKS, Amazon S3, AWS Systems Manager, Amazon EC2 Auto Scaling, and Amazon ECR, which the GitLab Runners will need to build and deploy the demo app.
+```
+cat << EOF > ~/environment/gitlab-runner-policy.json
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "EKSGetAllClusters",
+ "Effect": "Allow",
+ "Action": [
+ "eks:DescribeNodegroup",
+ "eks:ListNodegroups",
+ "eks:DescribeCluster",
+ "eks:ListClusters",
+ "eks:AccessKubernetesApi",
+ "ssm:GetParameter",
+ "eks:ListUpdates",
+ "eks:ListFargateProfiles"
+ ],
+ "Resource": "*"
+ },
+ {
+ "Sid": "ASGSelfAccess",
+ "Effect": "Allow",
+ "Action": [
+ "iam:ListAccountAliases",
+ "autoscaling:DescribeAutoScalingInstances",
+ "autoscaling:DescribeAutoScalingGroups",
+ "autoscaling:DescribeLifecycle*"
+ ],
+ "Resource": "*"
+ },
+ {
+ "Sid": "ASGLifeCycleAccess",
+ "Effect": "Allow",
+ "Action": [
+ "autoscaling:CompleteLifecycleAction",
+ "autoscaling:RecordLifecycleActionHeartbeat"
+ ],
+ "Resource": "arn:aws:autoscaling:*:*:autoScalingGroup:*:autoScalingGroupName/linux-docker-scaling-spotonly*"
+ },
+ {
+ "Sid": "AllowRunnerJobsToDoPredictiveScaling",
+ "Effect": "Allow",
+ "Action": [
+ "autoscaling:UpdateAutoScalingGroup"
+ ],
+ "Resource": "arn:aws:autoscaling:*:*:autoScalingGroup:*:autoScalingGroupName/linux-docker-scaling-spotonly*"
+ },
+ {
+ "Sid": "EC2SelfAccess",
+ "Effect": "Allow",
+ "Action": [
+ "ec2:DescribeInstances",
+ "ec2:DescribeTags"
+ ],
+ "Resource": "*"
+ },
+ {
+ "Sid": "S3CacheBucketAccess",
+ "Effect": "Allow",
+ "Action": [
+ "s3:GetObject",
+ "s3:GetObjectVersion",
+ "s3:GetBucketVersioning",
+ "s3:PutObject",
+ "s3:DeleteObject"
+ ],
+ "Resource": "arn:aws:s3:::*linux-docker-scaling-spotonly*"
+ },
+ {
+ "Sid": "ECRAccess",
+ "Effect": "Allow",
+ "Action": "ecr:GetAuthorizationToken",
+ "Resource": "*"
+ },
+ {
+ "Sid": "ECRAccessRepo",
+ "Effect": "Allow",
+ "Action": [
+ "ecr:BatchGetImage",
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:CompleteLayerUpload",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:InitiateLayerUpload",
+ "ecr:PutImage",
+ "ecr:UploadLayerPart"
+ ],
+ "Resource": "arn:aws:ecr:*:*:*gitlab-spot-demo*"
+ }
+ ]
+}
+EOF
+```
+
+4. Attach the inline access policy to the role using the put-role-policy command:
+```
+aws iam put-role-policy \
+ --role-name gitlab-runner \
+ --policy-name gitlab-runner-access \
+ --policy-document file://~/environment/gitlab-runner-policy.json
+```
+
+5. Attach the managed access policy to the role using the attach-role-policy command:
+```
+aws iam attach-role-policy \
+--policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \
+--role-name gitlab-runner
+
+aws iam attach-role-policy \
+--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore \
+--role-name gitlab-runner
+
+aws iam attach-role-policy \
+--policy-arn arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole \
+--role-name gitlab-runner
+
+aws iam attach-role-policy \
+--policy-arn arn:aws:iam::aws:policy/service-role/AmazonSSMMaintenanceWindowRole \
+--role-name gitlab-runner
+```
+
+6. Create an instance profile named gitlab-runner-profile using the create-instance-profile command.
+```
+aws iam create-instance-profile --instance-profile-name gitlab-runner
+```
+
+7. Add the gitlab-runner role to the gitlab-runner instance profile.
+```
+aws iam add-role-to-instance-profile \
+ --instance-profile-name gitlab-runner \
+ --role-name gitlab-runner
+```
+
+{{% notice warning %}}
+Note that here you are assigning permission policies as required for the demo app. In a real Production environment you should always follow the least privilege principle. You can find the best practices in [AWS Blogs](https://aws.amazon.com/blogs/security/techniques-for-writing-least-privilege-iam-policies/).
+{{% /notice %}}
+
+
+### Deploy the CloudFormation stack
+
+You will now get the runner configuration information from GitLab and then start the AWS CloudFormation stack deployment.
+
+1. Return to the browser tab with GitLab.
+2. In the **GitLab Spot Workshop** project choose **Settings** > **CI/CD** in the navigation pane.
+3. Expand the **Runners** section and save into a text file both GitLab URL and the registration token that are displayed on the screen:
+
+![GitLab Screenshot: Runners configuration](/images/gitlab-spot/GitLab-RunnersRegistration.png)
+
+4. Download the CloudFormation template latest version located [**here**](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/GitLabElasticScalingRunner.cf.yml).
+
+5. Return to the browser tab with CloudFormation or open it again using the search box at the top.
+6. Choose **Create stack** and in the dropdown choose **With new resources (standard)**.
+7. In the **Template source** field select **Upload a template file**, choose the `GitLabElasticScalingRunner.cf.yml` file you have just downloaded above, and choose **Next**.
+8. In the **Stack name** field enter `linux-docker-scaling-spotonly` (**extremely important**, as this name is used in the IAM policy above).
+
+**Challenge:** Investigate the parameters presented and try deploying the stack yourself. Expand the section below to see step-by-step instructions.
+{{%expand "Click to reveal the instructions" %}}
+9. In the **GitLab Instance URL** field enter GitLab URL you saved previously (in the format `https://xxx.cloudfront.net`).
+10. In the **One or more runner Registration tokens from the target instance.** field enter the token you saved previously.
+11. In the **The S3 bucket that will be used for a shared runner cache.** leave the default value (do not enter the GitLabCacheBucket value you saved from CloudFormation Output values, as at the moment the template will still try to create policies using the custom bucket and will fail if a different one is provided).
+12. In the **The number of instances that should be configured. Generally 1 for warm HA and 2 for hot HA.** enter `2`.
+13. In the **Override automatic IAM Instance Profile for a precreated one.** type `gitlab-runner`.
+14. In the **The VPC in the account and region should be used**, get the VPC ID (in the format `vpc-...`) by running below command in Cloud9:
+```
+echo VPC ID = $VPC
+```
+15. In the field **Second priority instance type to be used - if previous priorities are exhausted. Used for Ec2 Fleet when not using spot.** enter `m5a.large`. For the third and fourth priority enter `m5d.large` and `m5n.large` respectively.
+16. In the field **Whether to AutoScale the Scaling Group.** select `true` in the dropdown.
+17. In the field **Maximum instances in the Scaling Group.** type `10`.
+18. In the field **Scale in threshold (percent).** type `40`.
+19. In the field **How long the scale in threshold should be experienced before triggering scale out.** type `60`.
+20. In the field **Scale out threshold (percent).** type `70`.
+21. In the field **How long the scale out threshold should be experienced before triggering scale out.** type `60`.
+22. In the field **Percentage of non-spot instances when scaling above OnDemandBaseCapacity.** type `0`.
+23. Leave other parameters with their default values and choose **Next**, then once again **Next**.
+24. In the bottom of the screen mark the checkbox **I acknowledge that AWS CloudFormation might create IAM resources with custom names.**, and choose **Create stack**:
+![CloudFormation Console Screenshot: Create GitLab Runners stack](/images/gitlab-spot/AWSConsole-CloudFormationGitLabRunnersStack.png)
+24. Wait until the stack is in `CREATE_COMPLETE` status, which should take approximately 5 minutes.
+{{% /expand%}}
+
+{{% notice tip %}}
+You had to use the full template to customize the IAM role used by the runners. If your production scenario does not require this and you are fine with the default permissions, you can use one of the easy buttons (out-of-the-box scenarios) provided [**here**](https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg#easy-buttons-provided) to deploy the stack: this will require less parameters to customize.
+{{% /notice %}}
+
+25. Now you can open the EC2 console and verify that your runner(s) are using spot instances: open the corresponding EC2 instance (its name starts with `linux-docker-scaling-spotonly-`) and check the **Lifecycle** field:
+
+![EC2 Console Screenshot: Runner lifecycle](/images/gitlab-spot/AWSConsole-EC2RunnerLifecycle.png)
+
+26. Return to the browser tab with GitLab, refresh the CI/CD settings page and make sure that your runner(s) have appeared in the **Runners** section:
+
+![GitLab Screenshot: Runner available](/images/gitlab-spot/GitLab-RunnerAvailable.png)
+
+You can now proceed to build your application in [**Building the demo app**](/amazon-ec2-spot-cicd-workshop/gitlab-spot/040-building-demo-app.html).
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/040-building-demo-app.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/040-building-demo-app.md
new file mode 100644
index 00000000..b0e433f0
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/040-building-demo-app.md
@@ -0,0 +1,47 @@
++++
+title = "Building the demo app"
+weight = 40
++++
+
+In this lab you will push the changes to your origin repository and verify that the pipeline has successfully finished both in GitLab and by checking the image in Amazon ECR.
+
+1. Return to the browser tab with Cloud9 and execute the following command in the terminal. Specify `root` as the username and the same password you used in [**Create a GitLab repository**](020-create-gitlab-repo.html) to log in to GitLab:
+
+```
+git push -u origin main
+```
+
+2. Return to the browser tab with GitLab and in the navigation pane choose **CI/CD** > **Pipelines**.
+3. Make sure that the CI/CD pipeline is successfully completed or wait until it does. If there were any issues, open the failed stage by clicking the corresponding circle and check the detailed execution log:
+
+![GitLab Screenshot: Build pipeline completed](/images/gitlab-spot/GitLab-BuildPipeline.png)
+
+4. Return to the browser tab with AWS Console.
+5. Type `ECR` in the search box at the top and open the **Elastic Container Registry** service.
+6. Open the repository with **gitlab-spot-demo** in its name and verify that it contains an image that has just been built in GitLab:
+
+![ECR Console Screenshot: Images](/images/gitlab-spot/AWSConsole-ECRImages.png)
+
+You have successfully built the image and can now proceed to [**Deploying Amazon EKS on Spot instances**](050-deploying-eks-on-spot.html).
+
+### Challenges
+
+If this and previous sections of the workshop seemed too easy, try completing the following challenges:
+
+**Challenge 1:** Configure shared runners for the whole GitLab CI/CD and not just the current repository. Create an additional repository and verify that your runners serve it too.
+
+{{%expand "Click to reveal a hint" %}}
+Open the Admin Area of GitLab interface and in the Runners section get the registration code. Use it instead of the one from the project.
+{{% /expand%}}
+
+**Challenge 2:** Trigger auto-scaling of the instances to get more runners created.
+
+{{%expand "Click to reveal a hint" %}}
+By default, auto-scaling is done by CPU load, so you can run a CPU load generator inside your build scripts to simulate it, for example, you can use `stress` or `stress-ng` tools for this.
+{{% /expand%}}
+
+**Challenge 3:** Modify the auto-scaling group to perform scaling by the number of the jobs instead of the CPU load.
+
+{{%expand "Click to reveal a hint" %}}
+Create a Lambda function that gets the number of jobs from [GitLab API](https://docs.gitlab.com/ee/api/jobs.html#list-project-jobs) and publishes it as a custom CloudWatch metric. Use this metric to scale the group.
+{{% /expand%}}
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/050-deploying-eks-on-spot.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/050-deploying-eks-on-spot.md
new file mode 100644
index 00000000..61b2a761
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/050-deploying-eks-on-spot.md
@@ -0,0 +1,103 @@
++++
+title = "Deploying Amazon EKS on Spot instances"
+weight = 50
++++
+
+You will now deploy an Amazon EKS cluster in order to roll out your newly built application into it. In this lab you will use [Terraform by HashiCorp](https://www.terraform.io/) for this purpose. However, you can also do it using AWS CloudFormation, AWS CDK or eksctl.
+
+The Terraform files are located in `~/environment/amazon-ec2-spot-cicd-workshop/gitlab-spot/eks-cluster` directory of your Cloud9 environment.
+
+1. Return to the browser tab with Cloud9 and execute the following commands in the terminal to download and install `kubectl` that you will be using to work with the Kubernetes cluster:
+```
+cd ~/environment
+export KUBECTL_VERSION=v1.23.7
+sudo curl --silent --location -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl
+sudo chmod +x /usr/local/bin/kubectl
+```
+
+2. Switch to the directory with IaC templates, initialize the Terraform working directory, and apply the changes:
+```
+cd ~/environment/amazon-ec2-spot-cicd-workshop/gitlab-spot/eks-cluster
+terraform init
+TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 600")
+export TF_VAR_aws_region=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region)
+curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json
+export TF_VAR_alb_policy=$(aws iam get-policy --policy-arn arn:aws:iam::$(aws sts get-caller-identity --output text --query Account):policy/AWSLoadBalancerControllerIAMPolicy --query Policy.Arn --output text 2>/dev/null || aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam-policy.json --query Policy.Arn --output text)
+terraform apply
+```
+
+3. When asked for the parameters, enter the following values:
+ * **kubernetes_version**: enter `1.23`
+ * **vpc_id**: open a new terminal in Cloud9 and run below command to get the VPC ID
+ ```
+ echo VPC ID = $VPC
+ ```
+
+![Cloud9 Console Screenshot: Terraform variables](/images/gitlab-spot/Cloud9-TerraformVars.png)
+
+4. When asked if you want to perform the changes, check the list of resources to be created, type `yes`, and press Enter. It should take approximately 15 minutes to deploy the cluster.
+5. After the process has finished, execute the following command to update the `kubeconfig`, using the information from Amazon EKS:
+```
+aws eks update-kubeconfig --region $TF_VAR_aws_region --name gitlab-spot-workshop
+```
+
+6. Execute the below commands to verify that you can reach Kubernetes API. The first should return all Kubernetes resources across the namespaces and the second should show 3 worker nodes:
+```
+kubectl get all -A
+kubectl get nodes
+```
+7. Execute the below command to install AWS Load Balancer Controller (it will be used to configure Application Load Balancer for your pods):
+```
+curl -sSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
+helm repo add eks https://aws.github.io/eks-charts
+helm install aws-load-balancer-controller eks/aws-load-balancer-controller --set clusterName=gitlab-spot-workshop -n kube-system
+```
+
+8. Return to the browser tab with EC2 console or open it again and make sure that the worker nodes are using spot instances: you can either select each instance called `spot_group` one-by-one and check the value of the **Lifecycle** field in the description tab below or enable its display in the main table by choosing the cogwheel icon in the top-right corner, searching for `Instance lifecycle` in the **Attribute columns** field, enabling the toggle next to it, and choosing **Confirm**:
+
+![EC2 Console Screenshot: Instance list preferences](/images/gitlab-spot/AWSConsole-EC2Preferences.png)
+
+![EC2 Console Screenshot: Instance list with instance lifecycle](/images/gitlab-spot/AWSConsole-EC2InstancesLifecycle.png)
+
+### Add GitLab role to Kubernetes RBAC
+By default, the IAM user or role that created Amazon EKS cluster gets access to its Kubernetes API. However, as you have different roles assigned to the Cloud9 environment and the GitLab runners, you need to add the latter to Kubernetes Role Based Access Control (RBAC).
+
+You will do it by manually modifying Kubernetes ConfigMap called `aws-auth`. You could have done it directly in Terraform, but in this workshop we suggest that you do it manually to better understand the concepts.
+
+1. Return to the browser tab with Cloud9 and in the terminal execute the following command, which will save the ConfigMap current manifest into `aws-auth.yaml` file:
+```
+cd ~/environment
+kubectl get configmap -n kube-system aws-auth -o yaml > aws-auth.yaml
+```
+
+2. To generate the lines that you will add into the file, execute the following commands:
+```
+export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
+cat << EoF
+ - rolearn: arn:aws:iam::${ACCOUNT_ID}:role/gitlab-runner
+ groups:
+ - system:masters
+ username: gitlab-runner
+EoF
+```
+
+3. Copy the output of the last command, then in the file tree on the left open the file `aws-auth.yaml` by double-clicking it, and finally add the previous command's output that you copied before into `mapRoles` section. You should have something similar to the below screenshot:
+
+![Cloud9 Console Screenshot: aws-auth ConfigMap](/images/gitlab-spot/Cloud9-AWSAuth.png)
+
+4. Save the file, using **Ctrl + S** or **Cmd + S** depending on your Operating System, or choosing **File** > **Save**. Then close it.
+5. Apply the Kubernetes manifest using the command below (you can ignore the warning about missing annotation):
+
+```
+kubectl apply -f aws-auth.yaml
+```
+
+You are now ready for the final steps to deploy your demo application into the cluster in [**Installing the demo app into Amazon EKS**](060-deploy-app-to-eks.html).
+
+### Challenge
+
+Instead of using Terraform try deploying the Amazon EKS cluster with eksctl.
+
+{{% notice tip %}}
+You can find step-by-step instructions in the [**Containers with EKS**](/using_ec2_spot_instances_with_eks/020_eksctl.html) workshop, but make sure you modify them to use the VPC you created in this workshop.
+{{% /notice %}}
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/060-deploy-app-to-eks.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/060-deploy-app-to-eks.md
new file mode 100644
index 00000000..3bfff4cd
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/060-deploy-app-to-eks.md
@@ -0,0 +1,115 @@
++++
+title = "Installing the demo app into Amazon EKS"
+weight = 60
++++
+
+In this section you will deploy the demo application you built earlier in the new Amazon EKS cluster deployed fully on spot instances.
+
+1. In the Cloud9 file tree on the left open file `amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/.gitlab-ci.yml` (if you don't see it, make sure you have enabled the hidden files in [**Workshop Preparation**](010-prep.html)).
+
+2. Change the jobs `deploy_to_eks` and `test_on_eks` to the following ones:
+```
+deploy_to_eks:
+ stage: deploy
+ image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
+ before_script:
+ - aws --version
+ - aws eks update-kubeconfig --region $REGION --name $K8S_CLUSTER_NAME
+ - apt-get install -y gettext # To get envsubst
+ - export KUBECTL_VERSION=v1.23.7
+ - curl --silent --location -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl
+ - chmod +x /usr/local/bin/kubectl
+ script:
+ - envsubst < k8s_deploy.yaml > k8s_deploy_filled.yaml
+ - kubectl apply -f k8s_deploy_filled.yaml
+ - kubectl rollout status deploy/spot-demo
+ - kubectl get services/spot-demo -o wide
+ - kubectl get ingress spot-demo -o wide
+ - echo "SERVICE_ADDRESS=$(kubectl get ingress spot-demo -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')" >> deploy_to_eks.env
+ artifacts:
+ reports:
+ dotenv: deploy_to_eks.env
+
+test_on_eks:
+ stage: test
+ image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
+ before_script:
+ - echo Verifying service on $SERVICE_ADDRESS
+ script:
+ - |
+ echo Waiting for the service to respond with 200
+ serviceup=0
+
+ for i in {1..60}
+ do
+ export result=$(curl -s -o /dev/null -w "%{http_code}" http://$SERVICE_ADDRESS/info/)
+
+ if [[ "$result" -eq 200 ]]
+ then
+ serviceup=1
+ break
+ fi
+
+ echo Load balancer not ready yet
+ sleep 10
+ done
+
+ if [[ "$serviceup" -eq 1 ]]
+ then
+ echo Service responded with 200
+ else
+ echo Service not responded within the timeout
+ exit 1
+ fi
+
+ for i in {1..50}
+ do
+ echo -n "Test $i: "
+ export result=$(curl -s http://$SERVICE_ADDRESS/info/ | egrep 'lifecycle.*spot' | wc -l)
+ if [[ "$result" -eq 1 ]]
+ then
+ echo -e "\033[0;32mOK\033[0m"
+ else
+ echo -e "\033[0;31mNOT OK\033[0m"
+ exit 1
+ fi
+ done
+ dependencies:
+ - deploy_to_eks
+```
+
+3. Save the file, using **Ctrl + S** or **Cmd + S** depending on your Operating System, or choosing **File** > **Save**. Then close it.
+
+4. Create a new commit with the updated file and push it to the origin:
+```
+cd ~/environment/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/
+git add .gitlab-ci.yml
+git commit -m "Added deployment to EKS"
+git push
+```
+
+5. Return to the browser tab with GitLab and in the navigation pane choose **CI/CD** > **Pipelines**.
+
+6. Make sure that the CI/CD pipeline is successfully completed or wait until it does.
+
+7. You can click on the circle to see the job output. For example, for the right-most one it would show the testing result:
+![GitLab Screenshot: GitLab Testing Job output](/images/gitlab-spot/GitLab-TestingJob.png)
+
+8. Return to the Cloud9 tab and print the information about the new service:
+```
+echo http://$(kubectl get ingress spot-demo -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')/info/
+```
+
+9. Open the output URL in a new browser tab and refresh the page several times to make sure that the requests reach pods on different worker nodes, all on spot instances:
+![Demo App Screenshot](/images/gitlab-spot/DemoApp.png)
+
+
+You have deployed the demo application in Kubernetes cluster (created in Amazon EKS service) with all its worker nodes running on Amazon EC2 Spot instances. It is useful for executing tests in your CI/CD pipeline (though AWS customers run full Production clusters on spot instances too): for example, you can add new spot nodes into your cluster right from the pipeline and after finishing testing remove them.
+
+To view the current economy from using spot instances instead of on-demand ones perform the following steps:
+
+1. Return to the browser tab with EC2 console or open it again.
+2. Choose **Spot Requests** in the **Instances** section of the navigation pane.
+3. Choose **Savings summary**.
+
+You can now clean all the resources created during the workshop using the steps in [**Workshop Cleanup**](070-cleanup.html).
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/070-cleanup.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/070-cleanup.md
new file mode 100644
index 00000000..c76f121d
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/070-cleanup.md
@@ -0,0 +1,34 @@
++++
+title = "Workshop Cleanup"
+weight = 70
++++
+
+You will now remove the resources created during the workshop.
+
+1. Return to the browser tab with Cloud9 and perform the following commands in the terminal to remove the Kubernetes ingress, service, and deployment:
+
+```
+kubectl delete ingress/spot-demo
+kubectl delete service/spot-demo
+kubectl delete deployment/spot-demo
+```
+
+2. Perform the following commands to remove the Amazon EKS cluster (when asked, use the same parameter values as when you were creating it: Kubernetes version and the VPC ID):
+
+```
+cd ~/environment/amazon-ec2-spot-cicd-workshop/gitlab-spot/eks-cluster
+TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 600")
+export TF_VAR_aws_region=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region)
+export TF_VAR_alb_policy=$(aws iam get-policy --policy-arn arn:aws:iam::$(aws sts get-caller-identity --output text --query Account):policy/AWSLoadBalancerControllerIAMPolicy --query Policy.Arn --output text)
+terraform destroy
+```
+
+3. When asked if you want to destroy all resources, type `yes` and press Enter. Wait until the process is finished and close your Cloud9 environment.
+4. In CloudFormation console delete the stack `linux-docker-scaling-spotonly`.
+5. In the IAM console remove the role `GitLabRunner` and the policies `GitLabRunnerPolicy` and `AWSLoadBalancerControllerIAMPolicy`, unless you had it before the workshop.
+6. In the ECR console open the repository and remove all images inside it (you do not need to remove the repository itself: it will be done automatically when removing the GitLab stack).
+7. In the S3 console find a bucket with `gitlabworkshopc9outputbucket` in its name and remove all objects inside it.
+8. If you created the GitLab stack in CloudFormation yourself, remove it too (if you used the one created automatically, you will not be able to delete it, so you can leave it as is).
+
+### Thank you
+At this point, we would like to thank you for attending this workshop.
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/_index.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/_index.md
new file mode 100644
index 00000000..2de0bcff
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/_index.md
@@ -0,0 +1,34 @@
+---
+title: "CI/CD and Test Workloads (GitLab) with EC2 Spot Instances"
+menuTitle: "GitLab"
+weight: 20
+pre: ""
+---
+
+## Overview
+In this workshop you will add runners on Amazon EC2 Spot instances to a pre-installed GitLab. Then you will build a containerized demo application on them and install it into Kubernetes cluster that also uses only spot instances as its worker nodes. In the end you will test it to verify the results.
+
+You can perform all workshop steps one-by-one to get to the expected results, but for better understanding of using Spot instances with GitLab we recommend that you also look into the used templates and result files and try modifying them additionally.
+
+Many workshop steps imply manual actions in the AWS console to better demonstrate the underlying concepts, but in a Production environment it is better to automate them using Infrastructure as Code (IaC), such as [AWS CloudFormation](https://aws.amazon.com/cloudformation/) and [AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/home.html).
+
+## Workshop Details
+This workshop will be broken down into a series of sections that flow on from each other (that is, you must complete each section before proceeding with the next). The whole flow looks as following:
+
+![GitLab on Spot workshop flow](/images/gitlab-spot/lab-flow.png)
+
+
+The exercises that will be covered are:
+
+* [Starting the workshop](gitlab-spot/before.html) where you will log in to AWS accounts and deploy GitLab, if it is not yet deployed
+* [Workshop Preparation](gitlab-spot/010-prep.html) where you will save GitLab access details and create an AWS Cloud9 environment to execute the workshop steps
+* [Create a GitLab repository](gitlab-spot/020-create-gitlab-repo.html) where you will create a repository in GitLab CI/CD and create a demo application.
+* [Configure GitLab runners on Spot instances](gitlab-spot/030-configure-gitlab-runners-on-spot.html) where you will deploy the GitLab Runners in an auto-scaling group on spot instances
+* [Building the demo app](gitlab-spot/040-building-demo-app.html) where you will push the changes and make sure that your pipeline executes successfully.
+* [Deploying Amazon EKS on Spot instances](gitlab-spot/050-deploying-eks-on-spot.html) where you will create a new Kubernetes cluster in Amazon EKS that will only have worker nodes on spot instances
+* [Installing the demo app into Amazon EKS](gitlab-spot/060-deploy-app-to-eks.html) where you will modify your GitLab CI/CD scripts to add a stage of deploying on Amazon EKS and test the result
+* [Workshop Cleanup](gitlab-spot/070-cleanup.html) where you will remove all the resources created during the workshop
+
+The final architecture that we will be building looks the following way:
+
+![GitLab on Spot workshop architecture diagram](/images/gitlab-spot/gitlab-spot-architecture.png)
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/_index.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/_index.md
new file mode 100644
index 00000000..9ed185d3
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/_index.md
@@ -0,0 +1,11 @@
++++
+title = "Starting the workshop"
+chapter = false
+weight = 10
++++
+
+To start the workshop, follow one of the following pages, depending on whether you are...
+
+{{% children %}}
+
+Once you are done with either setup, continue with [**Workshop Preparation**](010-prep.html).
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/aws_event.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/aws_event.md
new file mode 100644
index 00000000..e329e71e
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/aws_event.md
@@ -0,0 +1,48 @@
++++
+title = "...At an AWS event"
+weight = 10
++++
+
+{{% notice warning %}}
+Only complete this section if you are at an AWS hosted event (such as re:Invent, public workshop, Immersion Day, or any other event hosted by an AWS employee). If you are running the workshop on your own, go to: [Start the workshop on your own]({{< ref "/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/self_paced.md" >}})
+{{% /notice %}}
+
+### Login to the AWS Workshop Portal
+
+If you are at an AWS event, an AWS account created for you to use throughout the workshop. You will need the **Participant Hash** provided to you by the event's organizers.
+
+1. Connect to the portal by browsing to [https://dashboard.eventengine.run/](https://dashboard.eventengine.run/).
+2. Enter the Hash in the text box, and click **Accept Terms & Login** .
+3. Select one of the options to sign-in, for example **Email One-Time Password (OTP)** which would request you to type your e-mail address and enter a passcode that you receive.
+
+![Event Engine Screenshot: Sign in with](/images/gitlab-spot/EE-SignInMethod.png)
+
+### Get the SSH key and log in to AWS Console
+
+1. In the Team Dashboard screen, choose **SSH Key**.
+2. In the popup page, choose **Download Key**. You don't need the key to complete the labs, but might want to still have it if you decide to explore the environment.
+
+![Event Engine Screenshot: SSH Key](/images/gitlab-spot/EE-SSHKey.png)
+
+3. Close the popup and back in the Team Dashboard screen, choose **AWS Console**.
+4. In the popup page, choose **Open AWS Console**.
+5. Select the AWS region specified by your facilitator.
+
+You are now logged in to the AWS console in an account that was created for you, and will be available only throughout the workshop run time.
+
+### Open the pre-provisioned CloudFormation stack
+
+In the next section we will get the login details of the GitLab environment that was pre-provisioned for you via AWS CloudFormation. First, you need to find and open the CloudFormation stack: it is the oldest one in the account.
+
+{{%expand "Click to reveal detailed instructions" %}}
+1. In the AWS Console enter **CloudFormation** in the search box at the top of the screen and open the service:
+
+![AWS Console Screenshot: Search for CloudFormation](/images/gitlab-spot/AWSConsole-CloudFormationSearch.png)
+
+2. In the navigation pane on the left choose **Stacks**.
+3. You should see two stacks in the list: the one for AWS Cloud9 environment starting with `aws-cloud9-...` and the main one starting with `mod-...`. You will need the main stack (latter one), click on it.
+{{% /expand%}}
+
+If there is no CloudFormation stack present, provision it as specified in the section [**...On your own**](self_paced.html).
+
+You can now proceed to the [**Workshop Preparation**](/amazon-ec2-spot-cicd-workshop/gitlab-spot/010-prep.html) where you will save the required output values from the stack.
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/self_paced.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/self_paced.md
new file mode 100644
index 00000000..a7f2b5dc
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/self_paced.md
@@ -0,0 +1,43 @@
++++
+title = "...On your own"
+weight = 20
++++
+
+{{% notice warning %}}
+Only complete this section if you are running the workshop on your own or if you do not have a CloudFormation stack with GitLab available. If you are at an AWS hosted event (such as re:Invent, public workshop, Immersion Day, or any other event hosted by an AWS employee), go to [Start the workshop at an AWS event]({{< ref "/amazon-ec2-spot-cicd-workshop/gitlab-spot/before/aws_event.md" >}}).
+{{% /notice %}}
+
+### Preparation
+
+{{% notice warning %}}
+Your account must have the ability to create new IAM roles and scope other IAM permissions.
+{{% /notice %}}
+
+1. If you don't already have an AWS account with Administrator access: [create one now by clicking here](https://aws.amazon.com/getting-started/)
+2. Log in to [AWS Console](https://console.aws.amazon.com/) with an IAM user having administrator permissions.
+
+### Create an SSH key
+
+You don't need the key to complete the labs, but it is still configured when creating the instances, because you might want to explore the environment and log in to the provisioned instances. The following steps show how to create it.
+
+1. Open **EC2** service in the AWS Console.
+2. In the navigation pane choose **Key Pairs** in the **Network & Security** section.
+3. If there is already an existing SSH key and you have its private key, remember its name, otherwise create a new one:
+ * Choose **Create key pair**
+ * In the **Name** field enter `ee-default-key-pair`
+ * In the **Private key file format** list select `.pem` (even if you use Microsoft Windows: we will be uploading this key into an AWS Cloud9 environment)
+ * Choose **Create key pair**
+ * Save the .pem file as suggested by your browser
+
+### Deploy GitLab
+Now you will deploy a GitLab without any runners. As it is not the purpose of this workshop to dive deep into GitLab itself, the deployment will be fully automated using Infrastructure as Code template in AWS CloudFormation. It will deploy a VPC with two public subnets, an Amazon S3 bucket that you can configure as GitLab cache, an EC2 Auto Scaling group for GitLab, an Application Load Balancer and an Amazon CloudFront distribution to organize a secure access to it, an Amazon ECR repository for storing the container image, and a number of supplementary resources.
+
+1. Open **CloudFormation** service in the AWS Console.
+2. In the navigation pane choose **Stacks**.
+3. Choose **Create stack** and in the dropdown choose **With new resources (standard)**.
+4. Download the CloudFormation YAML-template from [this link](https://raw.githubusercontent.com/awslabs/ec2-spot-workshops/master/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/gitlab-deploy.yml).
+5. In the **Template source** field select **Upload a template file**, choose the file you saved in the step above, and choose **Next**.
+6. In the **Stack name** field enter `mod-gitlab-spot-workshop`, in the **EEKeyPair** field select `ee-default-key-pair` or the name of the key you used in the steps above. Leave the default values in other fields and choose **Next**.
+7. Choose **Next**.
+8. Mark the checkbox **I acknowledge that AWS CloudFormation might create IAM resources.** and choose **Create stack**.
+9. Wait until the stack is in `CREATE_COMPLETE` status (it should take approximately 15 minutes) and continue with [**Workshop Preparation**](/amazon-ec2-spot-cicd-workshop/gitlab-spot/010-prep.html).
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/ap-southeast-1.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/ap-southeast-1.md
new file mode 100644
index 00000000..658eb6cd
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/ap-southeast-1.md
@@ -0,0 +1,8 @@
+---
+title: "Singapore"
+chapter: false
+disableToc: true
+hidden: true
+---
+
+Create a Cloud9 Environment: [https://ap-southeast-1.console.aws.amazon.com/cloud9/home?region=ap-southeast-1](https://ap-southeast-1.console.aws.amazon.com/cloud9/home?region=ap-southeast-1)
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/eu-central-1.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/eu-central-1.md
new file mode 100644
index 00000000..789fcee4
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/eu-central-1.md
@@ -0,0 +1,8 @@
+---
+title: "Frankfurt"
+chapter: false
+disableToc: true
+hidden: true
+---
+
+Create a Cloud9 Environment: [https://eu-central-1.console.aws.amazon.com/cloud9/home?region=eu-central-1](https://eu-central-1.console.aws.amazon.com/cloud9/home?region=eu-central-1)
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/eu-west-1.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/eu-west-1.md
new file mode 100644
index 00000000..e3fced02
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/eu-west-1.md
@@ -0,0 +1,8 @@
+---
+title: "Ireland"
+chapter: false
+disableToc: true
+hidden: true
+---
+
+Create a Cloud9 Environment: [https://eu-west-1.console.aws.amazon.com/cloud9/home?region=eu-west-1](https://eu-west-1.console.aws.amazon.com/cloud9/home?region=eu-west-1)
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/us-east-1.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/us-east-1.md
new file mode 100644
index 00000000..ba4c2e30
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/us-east-1.md
@@ -0,0 +1,8 @@
+---
+title: "N.Virginia"
+chapter: false
+disableToc: true
+hidden: true
+---
+
+Create a Cloud9 Environment: [https://us-east-1.console.aws.amazon.com/cloud9/home?region=us-east-1](https://us-east-1.console.aws.amazon.com/cloud9/home?region=us-east-1)
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/us-east-2.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/us-east-2.md
new file mode 100644
index 00000000..5d96da8c
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/us-east-2.md
@@ -0,0 +1,8 @@
+---
+title: "Ohio"
+chapter: false
+disableToc: true
+hidden: true
+---
+
+Create a Cloud9 Environment: [https://us-east-2.console.aws.amazon.com/cloud9/home?region=us-east-2](https://us-east-2.console.aws.amazon.com/cloud9/home?region=us-east-2)
\ No newline at end of file
diff --git a/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/us-west-2.md b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/us-west-2.md
new file mode 100644
index 00000000..caf14265
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/gitlab-spot/cloud9/us-west-2.md
@@ -0,0 +1,8 @@
+---
+title: "Oregon"
+chapter: false
+disableToc: true
+hidden: true
+---
+
+Create a Cloud9 Environment: [https://us-west-2.console.aws.amazon.com/cloud9/home?region=us-west-2](https://us-west-2.console.aws.amazon.com/cloud9/home?region=us-west-2)
diff --git a/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/_index.md b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/_index.md
new file mode 100644
index 00000000..29ef7976
--- /dev/null
+++ b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/_index.md
@@ -0,0 +1,33 @@
+---
+title: "CI/CD and Test Workloads (Jenkins) with EC2 Spot Instances"
+menuTitle: "Jenkins"
+date: 2019-02-19T02:02:35
+weight: 10
+pre: ""
+---
+
+## Overview
+During this workshop, you'll get hands-on with Amazon EC2 Spot and discover architectural best practices through the lens of DevOps and CI/CD. You'll deploy Jenkins build agents and test environments on Spot instances at a fraction of the cost of on-demand instances. You'll also implement mechanisms to ensure that your CI/CD tooling recovers from spot market events by decoupling application state from your compute resources. Finally, you'll migrate your CI/CD environment to a containered environment to eke out maximum performance and cost efficiency. In addition to covering the ins and outs of Spot, we'll share some of the Spot-based mechanisms used by customers to reduce the cost of their test and production workloads.
+
+## Workshop Details
+This workshop will be broken down into a series of labs that flow on from each other (that is, you must complete each lab in order before proceeding with the next). The lab exercises that will be covered are:
+
+* Workshop preparation: Deploy pre-requisite resources through Amazon CloudFormation;
+* Lab 1: Reduce the cost of builds using Amazon EC2 Spot Fleet;
+* Lab 2: Deploy testing environments using Amazon EC2 Spot, Amazon CloudFormation & Amazon EC2 Launch Templates;
+* Lab 3: Externalize state data to add resiliency and reduce cost for your CI/CD tooling;
+* Lab 4: Using containers backed by Auto Scaling Groups comprised of both on-demand and Spot instances;
+* Workshop clean up.
+
+As a reminder, you should have a laptop device (Windows/OSX/Linux are supported - tablets are not appropriate) with the current version of Google Chrome or Mozilla Firefox installed. You should also have a clean AWS account, with **AdministratorAccess** policy-level access.
+
+This workshop should take between two and three hours to complete, depending on your proficiency with the AWS services being featured.
+
+#### Additional considerations when running this workshop in a corporate IT environment
+If you are running this workshop from a corporate IT environment, contact your Systems Administrator to ensure that you will be able to establish outbound Secure Shell (SSH) connections to an Internet host:
+
+* If you cannot establish SSH connections to Internet hosts (and do not have a suitable workaround), you will not be able to complete Labs 3 & 4;
+* If you can establish SSH connections to Internet hosts, obtain from your Systems Administrator the source IP address CIDR block that connections will be established from.
+
+If you access the Internet through a transparent proxy server running in your corporate IT environment and this proxy server uses a different source address than where SSH connections come from, additional configuration of AWS Security Groups will need to be carried out. The lab guide will indicate the configuration steps required when appropriate.
+
diff --git a/content/amazon-ec2-spot-cicd-workshop/clea.md b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/clea.md
similarity index 100%
rename from content/amazon-ec2-spot-cicd-workshop/clea.md
rename to content/amazon-ec2-spot-cicd-workshop/jenkins-spot/clea.md
diff --git a/content/amazon-ec2-spot-cicd-workshop/lab1.md b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab1.md
similarity index 99%
rename from content/amazon-ec2-spot-cicd-workshop/lab1.md
rename to content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab1.md
index 13f88e32..b2f1e3c8 100644
--- a/content/amazon-ec2-spot-cicd-workshop/lab1.md
+++ b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab1.md
@@ -95,4 +95,4 @@ Now it’s time to test out how Jenkins handles pushing builds to spot instances
4. After a period of around a five minutes after your builds have completed, one of the Spot instances should be terminated by the plugin - there's no need to wait for this to happen (take our word for it, but you can verify this later).
## PROCEED TO LAB 2
-Once you've verified that builds are succeeding and that your Spot Fleet is capable of scaling out to handle queued build jobs, you may proceed with [Lab 2](/amazon-ec2-spot-cicd-workshop/lab2.html).
+Once you've verified that builds are succeeding and that your Spot Fleet is capable of scaling out to handle queued build jobs, you may proceed with [Lab 2](/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab2.html).
diff --git a/content/amazon-ec2-spot-cicd-workshop/lab2/_index.md b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab2/_index.md
similarity index 99%
rename from content/amazon-ec2-spot-cicd-workshop/lab2/_index.md
rename to content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab2/_index.md
index f84e1da7..a5e5484c 100644
--- a/content/amazon-ec2-spot-cicd-workshop/lab2/_index.md
+++ b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab2/_index.md
@@ -102,4 +102,4 @@ Once the deploy stage has been completed successfully:
Once you've verified that that the Game of Life web application loads, return to Jenkins and click on the **Test** pipeline stage and click on **Proceed** to get the pipeline to process the Terminate stage. After this final stage has executed, go back to the CloudFormation console to verify that the GameOfLife stack is being deleted.
## PROCEED TO LAB 3
-Once your test environment has been terminated, you may proceed with [Lab 3](/amazon-ec2-spot-cicd-workshop/lab3.html).
+Once your test environment has been terminated, you may proceed with [Lab 3](/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab3.html).
diff --git a/content/amazon-ec2-spot-cicd-workshop/lab2/clfn.md b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab2/clfn.md
similarity index 100%
rename from content/amazon-ec2-spot-cicd-workshop/lab2/clfn.md
rename to content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab2/clfn.md
diff --git a/content/amazon-ec2-spot-cicd-workshop/lab2/lamb.md b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab2/lamb.md
similarity index 100%
rename from content/amazon-ec2-spot-cicd-workshop/lab2/lamb.md
rename to content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab2/lamb.md
diff --git a/content/amazon-ec2-spot-cicd-workshop/lab3.md b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab3.md
similarity index 99%
rename from content/amazon-ec2-spot-cicd-workshop/lab3.md
rename to content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab3.md
index 1bab0f1a..c51b4656 100644
--- a/content/amazon-ec2-spot-cicd-workshop/lab3.md
+++ b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab3.md
@@ -86,4 +86,4 @@ Once you've verified that your Spot Fleet is self-healing, you no longer have an
{{% /expand%}}
## PROCEED TO LAB 4
-Once your on-demand Jenkins instance has been terminated, you may proceed with [Lab 4](/amazon-ec2-spot-cicd-workshop/lab4.html).
+Once your on-demand Jenkins instance has been terminated, you may proceed with [Lab 4](/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab4.html).
diff --git a/content/amazon-ec2-spot-cicd-workshop/lab4.md b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab4.md
similarity index 99%
rename from content/amazon-ec2-spot-cicd-workshop/lab4.md
rename to content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab4.md
index 32bccd8e..d7a6fbac 100644
--- a/content/amazon-ec2-spot-cicd-workshop/lab4.md
+++ b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab4.md
@@ -152,4 +152,4 @@ As things stand now, your projects in Jenkins won't be able to be built - you've
{{% /expand%}}
## PROCEED TO WORKSHOP CLEANUP
-Once your Jenkins infrastructure is completely running in your ECS cluster, you've completed all of the labs in this workshop. Congratulations! You may now proceed with the [Workshop Cleanup](/amazon-ec2-spot-cicd-workshop/clea.html).
+Once your Jenkins infrastructure is completely running in your ECS cluster, you've completed all of the labs in this workshop. Congratulations! You may now proceed with the [Workshop Cleanup](/amazon-ec2-spot-cicd-workshop/jenkins-spot/clea.html).
diff --git a/content/amazon-ec2-spot-cicd-workshop/prep.md b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/prep.md
similarity index 99%
rename from content/amazon-ec2-spot-cicd-workshop/prep.md
rename to content/amazon-ec2-spot-cicd-workshop/jenkins-spot/prep.md
index f43b660c..b4114ec0 100644
--- a/content/amazon-ec2-spot-cicd-workshop/prep.md
+++ b/content/amazon-ec2-spot-cicd-workshop/jenkins-spot/prep.md
@@ -41,4 +41,4 @@ Be sure to give it a stack name of **SpotCICDWorkshop** and ensure that you supp
The stack should take around five minutes to deploy.
## PROCEED TO LAB 1
-Once the CloudFormation is in the process of being deployed, you've completed all of the preparation required to start the workshop, you may proceed with [Lab 1](/amazon-ec2-spot-cicd-workshop/lab1.html).
+Once the CloudFormation is in the process of being deployed, you've completed all of the preparation required to start the workshop, you may proceed with [Lab 1](/amazon-ec2-spot-cicd-workshop/jenkins-spot/lab1.html).
diff --git a/static/images/gitlab-spot/AWSConsole-Cloud9Environment.png b/static/images/gitlab-spot/AWSConsole-Cloud9Environment.png
new file mode 100644
index 00000000..4ed370a9
Binary files /dev/null and b/static/images/gitlab-spot/AWSConsole-Cloud9Environment.png differ
diff --git a/static/images/gitlab-spot/AWSConsole-CloudFormationGitLabRunnersStack.png b/static/images/gitlab-spot/AWSConsole-CloudFormationGitLabRunnersStack.png
new file mode 100644
index 00000000..9e4146b0
Binary files /dev/null and b/static/images/gitlab-spot/AWSConsole-CloudFormationGitLabRunnersStack.png differ
diff --git a/static/images/gitlab-spot/AWSConsole-CloudFormationSearch.png b/static/images/gitlab-spot/AWSConsole-CloudFormationSearch.png
new file mode 100644
index 00000000..acf053ab
Binary files /dev/null and b/static/images/gitlab-spot/AWSConsole-CloudFormationSearch.png differ
diff --git a/static/images/gitlab-spot/AWSConsole-CloudFormationStackOutput.png b/static/images/gitlab-spot/AWSConsole-CloudFormationStackOutput.png
new file mode 100644
index 00000000..39c3c8d0
Binary files /dev/null and b/static/images/gitlab-spot/AWSConsole-CloudFormationStackOutput.png differ
diff --git a/static/images/gitlab-spot/AWSConsole-EC2InstancesLifecycle.png b/static/images/gitlab-spot/AWSConsole-EC2InstancesLifecycle.png
new file mode 100644
index 00000000..b637f864
Binary files /dev/null and b/static/images/gitlab-spot/AWSConsole-EC2InstancesLifecycle.png differ
diff --git a/static/images/gitlab-spot/AWSConsole-EC2Preferences.png b/static/images/gitlab-spot/AWSConsole-EC2Preferences.png
new file mode 100644
index 00000000..c09d36b4
Binary files /dev/null and b/static/images/gitlab-spot/AWSConsole-EC2Preferences.png differ
diff --git a/static/images/gitlab-spot/AWSConsole-EC2RunnerLifecycle.png b/static/images/gitlab-spot/AWSConsole-EC2RunnerLifecycle.png
new file mode 100644
index 00000000..b4ba6676
Binary files /dev/null and b/static/images/gitlab-spot/AWSConsole-EC2RunnerLifecycle.png differ
diff --git a/static/images/gitlab-spot/AWSConsole-ECRImages.png b/static/images/gitlab-spot/AWSConsole-ECRImages.png
new file mode 100644
index 00000000..03d3780f
Binary files /dev/null and b/static/images/gitlab-spot/AWSConsole-ECRImages.png differ
diff --git a/static/images/gitlab-spot/AWSConsole-IAMCreatePolicy.png b/static/images/gitlab-spot/AWSConsole-IAMCreatePolicy.png
new file mode 100644
index 00000000..2ea81b4b
Binary files /dev/null and b/static/images/gitlab-spot/AWSConsole-IAMCreatePolicy.png differ
diff --git a/static/images/gitlab-spot/Cloud9-AWSAuth.png b/static/images/gitlab-spot/Cloud9-AWSAuth.png
new file mode 100644
index 00000000..1d1dc546
Binary files /dev/null and b/static/images/gitlab-spot/Cloud9-AWSAuth.png differ
diff --git a/static/images/gitlab-spot/Cloud9-CallerIdentity.png b/static/images/gitlab-spot/Cloud9-CallerIdentity.png
new file mode 100644
index 00000000..b0fc5672
Binary files /dev/null and b/static/images/gitlab-spot/Cloud9-CallerIdentity.png differ
diff --git a/static/images/gitlab-spot/Cloud9-GitLabCI.png b/static/images/gitlab-spot/Cloud9-GitLabCI.png
new file mode 100644
index 00000000..68cc9e4d
Binary files /dev/null and b/static/images/gitlab-spot/Cloud9-GitLabCI.png differ
diff --git a/static/images/gitlab-spot/Cloud9-Initial.png b/static/images/gitlab-spot/Cloud9-Initial.png
new file mode 100644
index 00000000..71f5b7f2
Binary files /dev/null and b/static/images/gitlab-spot/Cloud9-Initial.png differ
diff --git a/static/images/gitlab-spot/Cloud9-Preferences.png b/static/images/gitlab-spot/Cloud9-Preferences.png
new file mode 100644
index 00000000..85f5cf32
Binary files /dev/null and b/static/images/gitlab-spot/Cloud9-Preferences.png differ
diff --git a/static/images/gitlab-spot/Cloud9-ShowHiddenFiles.png b/static/images/gitlab-spot/Cloud9-ShowHiddenFiles.png
new file mode 100644
index 00000000..91b8ef7a
Binary files /dev/null and b/static/images/gitlab-spot/Cloud9-ShowHiddenFiles.png differ
diff --git a/static/images/gitlab-spot/Cloud9-TerraformVars.png b/static/images/gitlab-spot/Cloud9-TerraformVars.png
new file mode 100644
index 00000000..6c10462d
Binary files /dev/null and b/static/images/gitlab-spot/Cloud9-TerraformVars.png differ
diff --git a/static/images/gitlab-spot/DemoApp.png b/static/images/gitlab-spot/DemoApp.png
new file mode 100644
index 00000000..d875c718
Binary files /dev/null and b/static/images/gitlab-spot/DemoApp.png differ
diff --git a/static/images/gitlab-spot/EE-SSHKey.png b/static/images/gitlab-spot/EE-SSHKey.png
new file mode 100644
index 00000000..c975018f
Binary files /dev/null and b/static/images/gitlab-spot/EE-SSHKey.png differ
diff --git a/static/images/gitlab-spot/EE-SignInMethod.png b/static/images/gitlab-spot/EE-SignInMethod.png
new file mode 100644
index 00000000..6edfc009
Binary files /dev/null and b/static/images/gitlab-spot/EE-SignInMethod.png differ
diff --git a/static/images/gitlab-spot/GitLab-BuildPipeline.png b/static/images/gitlab-spot/GitLab-BuildPipeline.png
new file mode 100644
index 00000000..a4d93f5c
Binary files /dev/null and b/static/images/gitlab-spot/GitLab-BuildPipeline.png differ
diff --git a/static/images/gitlab-spot/GitLab-CreateBlankProject.png b/static/images/gitlab-spot/GitLab-CreateBlankProject.png
new file mode 100644
index 00000000..cc1ba2db
Binary files /dev/null and b/static/images/gitlab-spot/GitLab-CreateBlankProject.png differ
diff --git a/static/images/gitlab-spot/GitLab-RunnerAvailable.png b/static/images/gitlab-spot/GitLab-RunnerAvailable.png
new file mode 100644
index 00000000..11f554a7
Binary files /dev/null and b/static/images/gitlab-spot/GitLab-RunnerAvailable.png differ
diff --git a/static/images/gitlab-spot/GitLab-RunnersRegistration.png b/static/images/gitlab-spot/GitLab-RunnersRegistration.png
new file mode 100644
index 00000000..cdbd8000
Binary files /dev/null and b/static/images/gitlab-spot/GitLab-RunnersRegistration.png differ
diff --git a/static/images/gitlab-spot/GitLab-TestingJob.png b/static/images/gitlab-spot/GitLab-TestingJob.png
new file mode 100644
index 00000000..bc21fbc8
Binary files /dev/null and b/static/images/gitlab-spot/GitLab-TestingJob.png differ
diff --git a/static/images/gitlab-spot/gitlab-spot-architecture.png b/static/images/gitlab-spot/gitlab-spot-architecture.png
new file mode 100644
index 00000000..9c76e0aa
Binary files /dev/null and b/static/images/gitlab-spot/gitlab-spot-architecture.png differ
diff --git a/static/images/gitlab-spot/lab-flow.png b/static/images/gitlab-spot/lab-flow.png
new file mode 100644
index 00000000..f3426cc9
Binary files /dev/null and b/static/images/gitlab-spot/lab-flow.png differ
diff --git a/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/Dockerfile b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/Dockerfile
new file mode 100644
index 00000000..0c0c6a70
--- /dev/null
+++ b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/Dockerfile
@@ -0,0 +1,10 @@
+FROM python:3.7-alpine
+
+RUN mkdir /app
+WORKDIR /app
+ADD . /app/
+
+RUN pip install -r requirements.txt
+
+EXPOSE 5000
+CMD ["python", "/app/app.py"]
\ No newline at end of file
diff --git a/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/app.py b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/app.py
new file mode 100644
index 00000000..8f1c9c7b
--- /dev/null
+++ b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/app.py
@@ -0,0 +1,56 @@
+import platform
+import requests
+import socket
+
+from flask import Flask, request
+
+app = Flask(__name__)
+
+@app.route('/info/')
+def info():
+ result = dict()
+
+ result['hostname'] = socket.gethostname()
+ result['platform'] = platform.platform()
+
+ try:
+ headers = {'X-aws-ec2-metadata-token-ttl-seconds': '60'}
+ req = requests.put('http://169.254.169.254/latest/api/token', headers=headers, timeout=0.3)
+ if req.status_code == 200:
+ token = req.text
+ else:
+ return result
+ except:
+ result['instance-hostname'] = 'undefined'
+
+ headers_get = {'X-aws-ec2-metadata-token': token}
+ try:
+ req = requests.get('http://169.254.169.254/latest/meta-data/hostname', headers=headers_get, timeout=0.3)
+ if req.status_code == 200:
+ result['instance-hostname'] = req.text
+ else:
+ result['instance-hostname'] = 'undefined'
+ except:
+ result['instance-hostname'] = 'undefined'
+
+ try:
+ req = requests.get('http://169.254.169.254/latest/meta-data/instance-id', headers=headers_get, timeout=0.3)
+ if req.status_code == 200:
+ result['instance-id'] = req.text
+ else:
+ result['instance-id'] = 'undefined'
+ except:
+ result['instance-id'] = 'undefined'
+
+ try:
+ req = requests.get('http://169.254.169.254/latest/meta-data/instance-life-cycle', headers=headers_get, timeout=0.3)
+ if req.status_code == 200:
+ result['lifecycle'] = req.text
+ else:
+ result['lifecycle'] = 'undefined'
+ except:
+ result['lifecycle'] = 'undefined'
+
+ return result
+
+app.run(debug=True, host='0.0.0.0')
\ No newline at end of file
diff --git a/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/k8s_deploy.yaml b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/k8s_deploy.yaml
new file mode 100644
index 00000000..e0ed39cd
--- /dev/null
+++ b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/k8s_deploy.yaml
@@ -0,0 +1,54 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: spot-demo
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: spot-demo
+ template:
+ metadata:
+ labels:
+ app: spot-demo
+ spec:
+ containers:
+ - name: spot-demo
+ image: "${ECR_REGISTRY}/${IMAGE_TAG}"
+ imagePullPolicy: Always
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: spot-demo
+ labels:
+ app: spot-demo
+spec:
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 5000
+ type: NodePort
+ selector:
+ app: spot-demo
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: spot-demo
+ annotations:
+ alb.ingress.kubernetes.io/scheme: internet-facing
+ alb.ingress.kubernetes.io/healthcheck-path: /info/
+spec:
+ ingressClassName: alb
+ rules:
+ - http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: spot-demo
+ port:
+ number: 80
\ No newline at end of file
diff --git a/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/requirements.txt b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/requirements.txt
new file mode 100644
index 00000000..c521ff87
--- /dev/null
+++ b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/requirements.txt
@@ -0,0 +1,3 @@
+Flask
+Flask-RESTful
+requests
\ No newline at end of file
diff --git a/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/template-gitlab-ci.yml b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/template-gitlab-ci.yml
new file mode 100644
index 00000000..ccc0e538
--- /dev/null
+++ b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/demo-app/template-gitlab-ci.yml
@@ -0,0 +1,42 @@
+variables:
+ ECR_REGISTRY: ${ECR_ADDRESS}
+ IMAGE_TAG: ${ECR_NAME}:$CI_COMMIT_SHA
+ REGION: ${AWS_REGION}
+ K8S_CLUSTER_NAME: gitlab-spot-workshop
+ DOCKER_DRIVER: overlay2
+ DOCKER_TLS_CERTDIR: ""
+
+stages:
+- build
+- deploy
+- test
+
+build_image:
+ stage: build
+ image: docker:20.10.6
+ before_script:
+ - docker info
+ - apk add --no-cache curl python3 py3-pip
+ - |
+ export TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 600")
+ - |
+ curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/hostname
+ - |
+ curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region
+ - pip install awscli
+ - aws --version
+ - aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ECR_REGISTRY
+ script:
+ - docker build -t $IMAGE_TAG .
+ - docker tag $IMAGE_TAG $ECR_REGISTRY/$IMAGE_TAG
+ - docker push $ECR_REGISTRY/$IMAGE_TAG
+
+deploy_to_eks:
+ stage: deploy
+ script:
+ - echo "Ok!"
+
+test_on_eks:
+ stage: test
+ script:
+ - echo "Ok!"
\ No newline at end of file
diff --git a/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/eks-cluster/main.tf b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/eks-cluster/main.tf
new file mode 100644
index 00000000..8e70d119
--- /dev/null
+++ b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/eks-cluster/main.tf
@@ -0,0 +1,100 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 4.12.1"
+ }
+ kubernetes = {
+ source = "hashicorp/kubernetes"
+ version = "~> 2.11.0"
+ }
+ }
+}
+
+provider "aws" {
+ region = var.aws_region
+}
+
+locals {
+ cluster_name = "gitlab-spot-workshop"
+}
+
+module "eks" {
+ source = "terraform-aws-modules/eks/aws"
+ version = "18.20.5"
+ cluster_name = local.cluster_name
+ cluster_version = var.kubernetes_version
+ subnet_ids = data.aws_subnets.eks_subnets.ids
+
+ tags = {
+ Project = "GitLabSpotWorkshop"
+ }
+
+ vpc_id = var.vpc_id
+
+ eks_managed_node_group_defaults = {
+ ami_type = "AL2_x86_64"
+ disk_size = 40
+ }
+
+ node_security_group_additional_rules = {
+ webhook_ingress = {
+ description = "Allow cluster to call webhooks"
+ protocol = "tcp"
+ from_port = 9443
+ to_port = 9443
+ type = "ingress"
+ source_cluster_security_group = true
+ }
+ all_ingress_self = {
+ description = "Allow nodes in the cluster to communicate with each other"
+ protocol = "-1"
+ from_port = 0
+ to_port = 0
+ type = "ingress"
+ self = true
+ }
+ all_egress_self = {
+ description = "Allow nodes in the cluster to communicate with each other"
+ protocol = "-1"
+ from_port = 0
+ to_port = 0
+ type = "egress"
+ self = true
+ }
+ }
+
+ eks_managed_node_groups = {
+ spot_group = {
+ desired_size = 3
+ max_size = 5
+ min_size = 1
+
+ instance_types = ["m4.large", "m5.large", "m5a.large", "m6i.large"]
+ capacity_type = "SPOT"
+
+ iam_role_additional_policies = [var.alb_policy]
+ }
+ }
+}
+
+data "aws_subnets" "eks_subnets" {
+ filter {
+ name = "vpc-id"
+ values = [var.vpc_id]
+ }
+}
+
+data "aws_eks_cluster" "cluster" {
+ name = module.eks.cluster_id
+}
+
+data "aws_eks_cluster_auth" "cluster" {
+ name = module.eks.cluster_id
+}
+
+provider "kubernetes" {
+ host = data.aws_eks_cluster.cluster.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
+ token = data.aws_eks_cluster_auth.cluster.token
+}
diff --git a/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/eks-cluster/variables.tf b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/eks-cluster/variables.tf
new file mode 100644
index 00000000..fe7e7e98
--- /dev/null
+++ b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/eks-cluster/variables.tf
@@ -0,0 +1,19 @@
+variable "alb_policy" {
+ type = string
+ description = "IAM policy for AWS Load Balancer Controller"
+}
+
+variable "aws_region" {
+ type = string
+ description = "AWS region"
+}
+
+variable "kubernetes_version" {
+ type = string
+ description = "Kubernetes version for the cluster"
+}
+
+variable "vpc_id" {
+ type = string
+ description = "VPC ID to create the cluster"
+}
\ No newline at end of file
diff --git a/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/gitlab-deploy.yml b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/gitlab-deploy.yml
new file mode 100644
index 00000000..3d13bff4
--- /dev/null
+++ b/workshops/amazon-ec2-spot-cicd-workshop/gitlab-spot/gitlab-deploy.yml
@@ -0,0 +1,660 @@
+AWSTemplateFormatVersion: 2010-09-09
+Description: GitLab Spot Workshop
+
+Parameters:
+ EEKeyPair:
+ Description: Name of the EC2 key for GitLab
+ Type: 'AWS::EC2::KeyPair::KeyName'
+ EETeamRoleArn:
+ Description: ARN of the user / role that will be owner of Cloud9 environment. Leave empty to use the current entity
+ Type: String
+ AmiId:
+ Description: Do not change
+ Type: 'AWS::SSM::Parameter::Value'
+ Default: '/aws/service/canonical/ubuntu/server/focal/stable/current/amd64/hvm/ebs-gp2/ami-id'
+ VpcCIDR:
+ Description: CIDR for the VPC
+ Type: String
+ Default: 10.0.0.0/16
+ Subnet1CIDR:
+ Description: CIDR for the first public subnet
+ Type: String
+ Default: 10.0.10.0/24
+ Subnet2CIDR:
+ Description: CIDR for the second public subnet
+ Type: String
+ Default: 10.0.20.0/24
+ EnvironmentName:
+ Description: Project name for the tags
+ Type: String
+ Default: GitLabSpotWorkshop
+ AllowedPattern : '^[a-zA-Z0-9\-]+$'
+
+Conditions:
+ EmptyC9Owner: !Equals [!Ref EETeamRoleArn, '']
+ IsEERole: !Equals [!Ref EETeamRoleArn, !Sub 'arn:aws:iam::${AWS::AccountId}:role/TeamRole']
+
+Resources:
+ VPC:
+ Type: AWS::EC2::VPC
+ Properties:
+ CidrBlock: !Ref VpcCIDR
+ EnableDnsSupport: true
+ EnableDnsHostnames: true
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} VPC
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ InternetGateway:
+ Type: AWS::EC2::InternetGateway
+ Properties:
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} Internet Gateway
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ InternetGatewayAttachment:
+ Type: AWS::EC2::VPCGatewayAttachment
+ Properties:
+ InternetGatewayId: !Ref InternetGateway
+ VpcId: !Ref VPC
+
+ Subnet1:
+ Type: AWS::EC2::Subnet
+ Properties:
+ VpcId: !Ref VPC
+ AvailabilityZone: !Select [ 0, !GetAZs '' ]
+ CidrBlock: !Ref Subnet1CIDR
+ MapPublicIpOnLaunch: true
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} Subnet 1
+ - Key: Project
+ Value: !Ref EnvironmentName
+ - Key: kubernetes.io/cluster/gitlab-spot-workshop
+ Value: shared
+ - Key: kubernetes.io/role/elb
+ Value: 1
+
+ Subnet2:
+ Type: AWS::EC2::Subnet
+ Properties:
+ VpcId: !Ref VPC
+ AvailabilityZone: !Select [ 1, !GetAZs '' ]
+ CidrBlock: !Ref Subnet2CIDR
+ MapPublicIpOnLaunch: true
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} Subnet 2
+ - Key: Project
+ Value: !Ref EnvironmentName
+ - Key: kubernetes.io/cluster/gitlab-spot-workshop
+ Value: shared
+ - Key: kubernetes.io/role/elb
+ Value: 1
+
+ PublicRouteTable:
+ Type: AWS::EC2::RouteTable
+ Properties:
+ VpcId: !Ref VPC
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} Public Route Table
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ DefaultPublicRoute:
+ Type: AWS::EC2::Route
+ DependsOn: InternetGatewayAttachment
+ Properties:
+ RouteTableId: !Ref PublicRouteTable
+ DestinationCidrBlock: 0.0.0.0/0
+ GatewayId: !Ref InternetGateway
+
+ Subnet1RouteTableAssociation:
+ Type: AWS::EC2::SubnetRouteTableAssociation
+ Properties:
+ RouteTableId: !Ref PublicRouteTable
+ SubnetId: !Ref Subnet1
+
+ Subnet2RouteTableAssociation:
+ Type: AWS::EC2::SubnetRouteTableAssociation
+ Properties:
+ RouteTableId: !Ref PublicRouteTable
+ SubnetId: !Ref Subnet2
+
+ EC2SecurityGroup:
+ Type: AWS::EC2::SecurityGroup
+ Properties:
+ GroupDescription: SSH and HTTP
+ VpcId:
+ Ref: VPC
+ SecurityGroupIngress:
+ - IpProtocol: tcp
+ FromPort: 22
+ ToPort: 22
+ CidrIp: !Ref VpcCIDR
+ - IpProtocol: tcp
+ FromPort: 80
+ ToPort: 80
+ SourceSecurityGroupId:
+ Ref: ELBSecurityGroup
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} EC2 Security Group
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ ELBSecurityGroup:
+ Type: AWS::EC2::SecurityGroup
+ Properties:
+ GroupDescription: HTTP only
+ VpcId:
+ Ref: VPC
+ SecurityGroupIngress:
+ - IpProtocol: tcp
+ FromPort: 80
+ ToPort: 80
+ CidrIp: 0.0.0.0/0
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} ELB Security Group
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ LambdaExecutionRole:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - lambda.amazonaws.com
+ Action:
+ - sts:AssumeRole
+ Policies:
+ -
+ PolicyName: allowLambdaLogging
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ -
+ Effect: "Allow"
+ Action:
+ - "logs:*"
+ Resource: "*"
+
+ RandomStringLambdaFunction:
+ Type: AWS::Lambda::Function
+ Properties:
+ Architectures:
+ - arm64
+ Code:
+ ZipFile: |
+ import json
+ import cfnresponse
+ import random
+ import string
+
+ def handler(event, context):
+ length = int(event["ResourceProperties"]["Length"])
+
+ responseData = {}
+ responseData["RandomString"] = "".join(random.choices(string.ascii_letters + string.digits, k=length))
+
+ cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
+ Handler: index.handler
+ Runtime: python3.8
+ Role: !GetAtt LambdaExecutionRole.Arn
+ MemorySize: 128
+ Timeout: 3
+ Tags:
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ GitLabPassword:
+ Type: AWS::CloudFormation::CustomResource
+ Properties:
+ Length: 16
+ ServiceToken: !GetAtt RandomStringLambdaFunction.Arn
+
+ LowerStringLambdaFunction:
+ Type: AWS::Lambda::Function
+ Properties:
+ Architectures:
+ - arm64
+ Code:
+ ZipFile: |
+ import json
+ import cfnresponse
+ import random
+ import string
+
+ def handler(event, context):
+ responseData = {}
+ responseData["OutputString"] = event["ResourceProperties"]["InputString"].lower()
+
+ cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
+ Handler: index.handler
+ Runtime: python3.8
+ Role: !GetAtt LambdaExecutionRole.Arn
+ MemorySize: 128
+ Timeout: 3
+ Tags:
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ EnvironmentNameLower:
+ Type: AWS::CloudFormation::CustomResource
+ Properties:
+ InputString: !Ref EnvironmentName
+ ServiceToken: !GetAtt LowerStringLambdaFunction.Arn
+
+ GitLabLaunchTemplate:
+ Type: AWS::EC2::LaunchTemplate
+ DependsOn: CloudFrontDistribution
+ Properties:
+ LaunchTemplateData:
+ ImageId: !Ref AmiId
+ InstanceType: m5.xlarge
+ KeyName: !Ref EEKeyPair
+ NetworkInterfaces:
+ - AssociatePublicIpAddress: true
+ DeviceIndex: 0
+ Groups:
+ - !Ref EC2SecurityGroup
+ UserData:
+ Fn::Base64: !Sub |
+ #!/bin/bash -xe
+ apt-get update
+ apt-get install -y curl openssh-server ca-certificates tzdata perl python3-setuptools
+ curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh | bash
+ GITLAB_ROOT_EMAIL="test@workshop.tld" GITLAB_ROOT_PASSWORD="${GitLabPassword.RandomString}" EXTERNAL_URL="http://${CloudFrontDistribution.DomainName}" apt-get install gitlab-ee
+ sed -i 's|http://${CloudFrontDistribution.DomainName}|https://${CloudFrontDistribution.DomainName}|' /etc/gitlab/gitlab.rb
+ echo "letsencrypt['enable'] = false" >> /etc/gitlab/gitlab.rb
+ echo "nginx['listen_port'] = 80" >> /etc/gitlab/gitlab.rb
+ echo "nginx['listen_https'] = false" >> /etc/gitlab/gitlab.rb
+ gitlab-ctl reconfigure
+ gitlab-rails runner "ApplicationSetting.last.update_attribute(:signup_enabled, false)"
+ wget https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
+ python3 /usr/lib/python3/dist-packages/easy_install.py --script-dir /opt/aws/bin aws-cfn-bootstrap-py3-latest.tar.gz
+ /opt/aws/bin/cfn-signal \
+ -e $? \
+ --stack ${AWS::StackName} \
+ --resource GitLabAutoScalingGroup \
+ --region ${AWS::Region}
+ TagSpecifications:
+ - ResourceType: instance
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} GitLab
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ GitLabAutoScalingGroup:
+ Type: AWS::AutoScaling::AutoScalingGroup
+ DependsOn: InternetGatewayAttachment
+ Properties:
+ LaunchTemplate:
+ LaunchTemplateId: !Ref GitLabLaunchTemplate
+ Version: !GetAtt GitLabLaunchTemplate.LatestVersionNumber
+ MinSize: 1
+ MaxSize: 1
+ TargetGroupARNs: [!Ref EC2TargetGroup]
+ VPCZoneIdentifier:
+ - Ref: Subnet1
+ - Ref: Subnet2
+ Tags:
+ - Key: Project
+ PropagateAtLaunch: false
+ Value: !Ref EnvironmentName
+ CreationPolicy:
+ ResourceSignal:
+ Timeout: PT45M
+ Count: 1
+
+ EC2TargetGroup:
+ Type: AWS::ElasticLoadBalancingV2::TargetGroup
+ Properties:
+ Matcher:
+ HttpCode: 200,302
+ Port: 80
+ Protocol: HTTP
+ VpcId: !Ref VPC
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} GitLab Target Group
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ ALBListener:
+ Type: AWS::ElasticLoadBalancingV2::Listener
+ Properties:
+ DefaultActions:
+ - Type: forward
+ TargetGroupArn:
+ Ref: EC2TargetGroup
+ LoadBalancerArn:
+ Ref: ApplicationLoadBalancer
+ Port: 80
+ Protocol: HTTP
+
+ ApplicationLoadBalancer:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Scheme: internet-facing
+ Subnets:
+ - Ref: Subnet1
+ - Ref: Subnet2
+ SecurityGroups:
+ - Ref: ELBSecurityGroup
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} Load Balancer
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ CloudFrontDistribution:
+ Type: AWS::CloudFront::Distribution
+ Properties:
+ DistributionConfig:
+ Origins:
+ - DomainName: !GetAtt ApplicationLoadBalancer.DNSName
+ Id: GitLabOrigin
+ CustomOriginConfig:
+ HTTPPort: 80
+ OriginProtocolPolicy: http-only
+ Enabled: true
+ DefaultCacheBehavior:
+ TargetOriginId: GitLabOrigin
+ AllowedMethods:
+ - DELETE
+ - GET
+ - HEAD
+ - OPTIONS
+ - PATCH
+ - POST
+ - PUT
+ CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
+ OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3
+ ViewerProtocolPolicy: redirect-to-https
+ ViewerCertificate:
+ CloudFrontDefaultCertificate: true
+ Tags:
+ - Key: Name
+ Value: !Sub ${EnvironmentName} CloudFront Distribution
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ DemoRepo:
+ Type: AWS::ECR::Repository
+ Properties:
+ RepositoryName: !Sub ${EnvironmentNameLower.OutputString}-gitlab-spot-demo
+ Tags:
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ GitLabCacheBucket:
+ Type: AWS::S3::Bucket
+
+ GitLabWorkshopC9Role:
+ Type: AWS::IAM::Role
+ Properties:
+ Tags:
+ - Key: Environment
+ Value: AWS Example
+ AssumeRolePolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - ec2.amazonaws.com
+ - ssm.amazonaws.com
+ Action:
+ - sts:AssumeRole
+ ManagedPolicyArns:
+ - arn:aws:iam::aws:policy/AdministratorAccess
+ Path: "/"
+
+ GitLabWorkshopC9LambdaExecutionRole:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - lambda.amazonaws.com
+ Action:
+ - sts:AssumeRole
+ Path: "/"
+ Policies:
+ - PolicyName:
+ Fn::Join:
+ - ''
+ - - GitLabWorkshopC9LambdaPolicy-
+ - Ref: EnvironmentName
+ - '-'
+ - Ref: AWS::Region
+ PolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Action:
+ - logs:CreateLogGroup
+ - logs:CreateLogStream
+ - logs:PutLogEvents
+ Resource: arn:aws:logs:*:*:*
+ - Effect: Allow
+ Action:
+ - cloudformation:DescribeStacks
+ - cloudformation:DescribeStackEvents
+ - cloudformation:DescribeStackResource
+ - cloudformation:DescribeStackResources
+ - ec2:DescribeInstances
+ - ec2:AssociateIamInstanceProfile
+ - ec2:ModifyInstanceAttribute
+ - ec2:ReplaceIamInstanceProfileAssociation
+ - iam:ListInstanceProfiles
+ - iam:PassRole
+ Resource: "*"
+
+ GitLabWorkshopC9BootstrapInstanceLambda:
+ Description: Bootstrap Cloud9 instance
+ Type: Custom::GitLabWorkshopC9BootstrapInstanceLambda
+ DependsOn:
+ - GitLabWorkshopC9BootstrapInstanceLambdaFunction
+ - GitLabWorkshopC9Instance
+ - GitLabWorkshopC9InstanceProfile
+ - GitLabWorkshopC9LambdaExecutionRole
+ Properties:
+ ServiceToken:
+ Fn::GetAtt:
+ - GitLabWorkshopC9BootstrapInstanceLambdaFunction
+ - Arn
+ REGION:
+ Ref: AWS::Region
+ StackName:
+ Ref: AWS::StackName
+ EnvironmentId:
+ Ref: GitLabWorkshopC9Instance
+ LabIdeInstanceProfileName:
+ Ref: GitLabWorkshopC9InstanceProfile
+ LabIdeInstanceProfileArn:
+ Fn::GetAtt:
+ - GitLabWorkshopC9InstanceProfile
+ - Arn
+
+ GitLabWorkshopC9BootstrapInstanceLambdaFunction:
+ Type: AWS::Lambda::Function
+ Properties:
+ Handler: index.lambda_handler
+ Role:
+ Fn::GetAtt:
+ - GitLabWorkshopC9LambdaExecutionRole
+ - Arn
+ Runtime: python3.8
+ MemorySize: 256
+ Timeout: 600
+ Code:
+ ZipFile: |
+ import boto3
+ import json
+ import time
+ import traceback
+ import cfnresponse
+
+ def lambda_handler(event, context):
+ # logger.info('event: {}'.format(event))
+ # logger.info('context: {}'.format(context))
+ responseData = {}
+
+ if event['RequestType'] == 'Create':
+ try:
+ # Open AWS clients
+ ec2 = boto3.client('ec2')
+
+ # Get the InstanceId of the Cloud9 IDE
+ instance = ec2.describe_instances(Filters=[{'Name': 'tag:aws:cloud9:environment','Values': [event['ResourceProperties']['EnvironmentId']]}])['Reservations'][0]['Instances'][0]
+ # logger.info('instance: {}'.format(instance))
+
+ # Create the IamInstanceProfile request object
+ iam_instance_profile = {
+ 'Arn': event['ResourceProperties']['LabIdeInstanceProfileArn'],
+ 'Name': event['ResourceProperties']['LabIdeInstanceProfileName']
+ }
+ # logger.info('iam_instance_profile: {}'.format(iam_instance_profile))
+
+ # Wait for Instance to become ready before adding Role
+ instance_state = instance['State']['Name']
+ # logger.info('instance_state: {}'.format(instance_state))
+ while instance_state != 'running':
+ time.sleep(5)
+ instance_state = ec2.describe_instances(InstanceIds=[instance['InstanceId']])
+ # logger.info('instance_state: {}'.format(instance_state))
+
+ # attach instance profile
+ response = ec2.associate_iam_instance_profile(IamInstanceProfile=iam_instance_profile, InstanceId=instance['InstanceId'])
+ # logger.info('response - associate_iam_instance_profile: {}'.format(response))
+ r_ec2 = boto3.resource('ec2')
+
+ responseData = {'Success': 'Started bootstrapping for instance: '+instance['InstanceId']}
+ cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID')
+
+ except Exception as e:
+ # logger.error(e, exc_info=True)
+ responseData = {'Error': traceback.format_exc()}
+ cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID')
+ else:
+ responseData = {'Success': 'Nothing to do'}
+ cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
+ Tags:
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ GitLabWorkshopC9InstanceProfile:
+ Type: AWS::IAM::InstanceProfile
+ Properties:
+ Path: "/"
+ Roles:
+ - Ref: GitLabWorkshopC9Role
+
+ GitLabWorkshopC9Instance:
+ Type: AWS::Cloud9::EnvironmentEC2
+ Properties:
+ Description: AWS Cloud9 instance for GitLab Spot Workshop
+ AutomaticStopTimeMinutes: 3600
+ InstanceType: t2.micro
+ OwnerArn: !If
+ - EmptyC9Owner
+ - !Ref AWS::NoValue
+ -
+ !If [IsEERole, !Sub 'arn:aws:sts::${AWS::AccountId}:assumed-role/TeamRole/MasterKey', !Ref EETeamRoleArn]
+ SubnetId: !Ref Subnet1
+ Tags:
+ - Key: Project
+ Value: !Ref EnvironmentName
+ - Key: SSMBootstrap
+ Value: Active
+
+ GitLabWorkshopC9SSMDocument:
+ Type: AWS::SSM::Document
+ Properties:
+ Content: Yaml
+ DocumentType: Command
+ Content:
+ schemaVersion: '2.2'
+ description: Bootstrap Cloud9 Instance
+ mainSteps:
+ - action: aws:runShellScript
+ name: GitLabWorkshopC9bootstrap
+ inputs:
+ runCommand:
+ - "#!/bin/bash"
+ - date
+ - echo LANG=en_US.utf-8 >> /etc/environment
+ - echo LC_ALL=en_US.UTF-8 >> /etc/environment
+ - . /home/ec2-user/.bashrc
+ - git clone https://github.com/awslabs/ec2-spot-workshops.git /home/ec2-user/environment/spot-workshop
+ - cp -ar /home/ec2-user/environment/spot-workshop/workshops/amazon-ec2-spot-cicd-workshop/ /home/ec2-user/environment/
+ - rm -rf /home/ec2-user/environment/spot-workshop/
+ - chown -R ec2-user:ec2-user /home/ec2-user/environment/amazon-ec2-spot-cicd-workshop/
+ - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/home/ec2-user/environment/awscliv2.zip"
+ - cd /home/ec2-user/environment
+ - unzip awscliv2.zip
+ - sudo ./aws/install
+ - sudo yum -y install jq
+ - echo "Bootstrap completed with return code $?"
+ Tags:
+ - Key: Project
+ Value: !Ref EnvironmentName
+
+ GitLabWorkshopC9OutputBucket:
+ Type: AWS::S3::Bucket
+ DeletionPolicy: Delete
+
+ GitLabWorkshopC9BootstrapAssociation:
+ Type: AWS::SSM::Association
+ DependsOn: GitLabWorkshopC9OutputBucket
+ Properties:
+ Name: !Ref GitLabWorkshopC9SSMDocument
+ OutputLocation:
+ S3Location:
+ OutputS3BucketName: !Ref GitLabWorkshopC9OutputBucket
+ OutputS3KeyPrefix: bootstrapoutput
+ Targets:
+ - Key: tag:SSMBootstrap
+ Values:
+ - Active
+
+Outputs:
+ VPC:
+ Description: The VPC with GitLab instance
+ Value: !Ref VPC
+
+ Subnet1:
+ Description: The subnet with GitLab instance
+ Value: !Ref Subnet1
+
+ Subnet1Zone:
+ Description: Availability zone of subnet 1
+ Value: !GetAtt Subnet1.AvailabilityZone
+
+ GitLabURL:
+ Description: URL to GitLab installation
+ Value: !Sub https://${CloudFrontDistribution.DomainName}
+
+ GitLabPassword:
+ Description: GitLab access credentials
+ Value: !GetAtt GitLabPassword.RandomString
+
+ GitLabCacheBucket:
+ Description: Cache for GitLab runners
+ Value: !Ref GitLabCacheBucket
\ No newline at end of file