From f3eae695d083d5d084734ed2dc7070d1914f71e8 Mon Sep 17 00:00:00 2001 From: Robert McCone Date: Wed, 31 Aug 2022 15:08:30 -0700 Subject: [PATCH 01/13] Added FIS experiment to interrupt Spot instances to the EKS/Karpenter workshop. Moved 'what we have learned' to FIS section and added a section to the cleanup for removing a cloudformation stack created for the FIS experiment --- .../karpenter/050_scaling/fis_experiment.md | 189 ++++++++++++++++++ content/karpenter/050_scaling/test_hpa.md | 19 -- content/karpenter/200_cleanup/_index.md | 5 + 3 files changed, 194 insertions(+), 19 deletions(-) create mode 100644 content/karpenter/050_scaling/fis_experiment.md diff --git a/content/karpenter/050_scaling/fis_experiment.md b/content/karpenter/050_scaling/fis_experiment.md new file mode 100644 index 00000000..f7be9108 --- /dev/null +++ b/content/karpenter/050_scaling/fis_experiment.md @@ -0,0 +1,189 @@ +--- +title: "Use FIS to Interrupt a Spot Instance" +date: 2022-08-31T13:12:00-07:00 +weight: 50 +--- + +In this section, you're going to create and run an experiment to [trigger the interruption of Amazon EC2 Spot Instances using AWS Fault Injection Simulator (FIS)](https://aws.amazon.com/blogs/compute/implementing-interruption-tolerance-in-amazon-ec2-spot-with-aws-fault-injection-simulator/). When using Spot Instances, you need to be prepared to be interrupted. With FIS, you can test the resiliency of your workload and validate that your application is reacting to the interruption notices that EC2 sends before terminating your instances. You can target individual Spot Instances or a subset of instances in clusters managed by services that tag your instances such as ASG, EC2 Fleet, and EKS. + +#### What do you need to get started? + +Before you start launching Spot interruptions with FIS, you need to create an experiment template. Here is where you define which resources you want to interrupt (targets), and when you want to interrupt the instance. + +Let's create a CloudFormation template which creates the IAM role (`FISSpotRole`) with the minimum permissions FIS needs to interrupt an instance, and the experiment template (`FISExperimentTemplate`) you're going to use to trigger a Spot interruption: + +``` +export FIS_EXP_NAME=fis-karpenter-spot-interruption +cat < fis-karpenter.yaml +AWSTemplateFormatVersion: 2010-09-09 +Description: FIS for Spot Instances +Parameters: + InstancesToInterrupt: + Description: Number of instances to interrupt + Default: 1 + Type: Number + + DurationBeforeInterruption: + Description: Number of minutes before the interruption + Default: 2 + Type: Number + +Resources: + + FISSpotRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: [fis.amazonaws.com] + Action: ["sts:AssumeRole"] + Path: / + Policies: + - PolicyName: root + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: 'ec2:DescribeInstances' + Resource: '*' + - Effect: Allow + Action: 'ec2:SendSpotInstanceInterruptions' + Resource: 'arn:aws:ec2:*:*:instance/*' + + FISExperimentTemplate: + Type: AWS::FIS::ExperimentTemplate + Properties: + Description: "Interrupt a spot instance with EKS label intent:apps" + Targets: + SpotIntances: + ResourceTags: + IntentLabel: apps + Filters: + - Path: State.Name + Values: + - running + ResourceType: aws:ec2:spot-instance + SelectionMode: !Join ["", ["COUNT(", !Ref InstancesToInterrupt, ")"]] + Actions: + interrupt: + ActionId: "aws:ec2:send-spot-instance-interruptions" + Description: "Interrupt a Spot instance" + Parameters: + durationBeforeInterruption: !Join ["", ["PT", !Ref DurationBeforeInterruption, "M"]] + Targets: + SpotInstances: SpotIntances + StopConditions: + - Source: none + RoleArn: !GetAtt FISSpotRole.Arn + Tags: + Name: "${FIS_EXP_NAME}" + +Outputs: + FISExperimentID: + Value: !GetAtt FISExperimentTemplate.Id +EoF +``` + +Here are some important notes about the template: + +* You can configure how many instances you want to interrupt with the `InstancesToInterrupt` parameter. In the template it's defined that it's going to interrupt **one** instance. +* You can also configure how much time you want the experiment to run with the `DurationBeforeInterruption` parameter. By default, it's going to take two minutes. This means that as soon as you launch the experiment, the instance is going to receive the two-minute notification Spot interruption warning. +* The most important section is the `Targets` from the experiment template. Under `ResourceTags` we have `IntentLabel: apps` which tells the experiment to only select from the EKS nodes we have labeled with `intent: apps`. If there is more than one instance still running with this label, the instance to be interrupted will be **chosen randomly**. + +#### Create the EC2 Spot Interruption Experiment with FIS + +Run the following commands to create the FIS experiment from your template, it will take a few moments for them to complete: + +``` +aws cloudformation create-stack --stack-name $FIS_EXP_NAME --template-body file://fis-karpenter.yaml --capabilities CAPABILITY_NAMED_IAM +aws cloudformation wait stack-create-complete --stack-name $FIS_EXP_NAME +``` + +#### Run the Spot Interruption Experiment + +You can run the Spot interruption experiment by issuing the following commands: + +``` +FIS_EXP_TEMP_ID=$(aws cloudformation describe-stacks --stack-name $FIS_EXP_NAME --query "Stacks[0].Outputs[?OutputKey=='FISExperimentID'].OutputValue" --output text) +FIS_EXP_ID=$(aws fis start-experiment --experiment-template-id $FIS_EXP_TEMP_ID --no-cli-pager --query "experiment.id" --output text) +``` + +In a few seconds the experiment should complete. This means one of your instances has received a two minute instance interruption notice and will be terminated. You can see the status of the experiment by running: + +``` +aws fis get-experiment --id $FIS_EXP_ID --no-cli-pager +``` + +If the experiment completed successfully you should see a response like this: + +``` +{ + "experiment": { + + ... + + "state": { + "status": "completed", + "reason": "Experiment completed." + }, + "targets": { + "SpotIntances": { + "resourceType": "aws:ec2:spot-instance", + "resourceTags": { + "IntentLabel": "apps" + }, + "filters": [ + { + "path": "State.Name", + "values": [ + "running" + ] + } + ], + "selectionMode": "COUNT(1)" + } + }, + + ... + + } +} +``` + +If `status` is listed as `running` or `pending`, wait a few seconds and run the command again. If `status` is listed as `failed` or `cancelled` with `reason` as either `Target resolution returned empty set` or `Action cancelled` it means you do not have any Spot instances running with the `intent: apps` label and so no instance was selected for termination. + +You can watch how your cluster reacts to the notice with kube-ops-view. Recall you can get the URL for your kube-ops-view by running: + +``` +kubectl get svc kube-ops-view | tail -n 1 | awk '{ print "Kube-ops-view URL = http://"$4 }' +``` + +{{% notice note %}} +You can interrupt more instances by running the experiment multiple times and watch how your cluster reacts, just reissue these commands: +``` +FIS_EXP_TEMP_ID=$(aws cloudformation describe-stacks --stack-name $FIS_EXP_NAME --query "Stacks[0].Outputs[?OutputKey=='FISExperimentID'].OutputValue" --output text) +FIS_EXP_ID=$(aws fis start-experiment --experiment-template-id $FIS_EXP_TEMP_ID --no-cli-pager --query "experiment.id" --output text) +``` +{{% /notice %}} + +## What Have we learned in this section : + +In this section we have learned: + +* We have built an container image using a multi-stage approach and uploaded the resulting microservice into Amazon Elastic Container Registry (ECR). + +* We have deployed a Monte Carlo Microservice applying all the lessons learned from the previous section. + +* We have set up the Horizontal Pod Autoscaler (HPA) to scale our Monte Carlo microservice whenever the average CPU percentage exceeds 50%, We configured it to scale from 3 replicas to 100 replicas + +* We have sent request to the Monte Carlo microservice to stress the CPU of the Pods where it runs. We saw in action dynamic scaling with HPA and Karpenter and now know can we appy this techniques to our kubernetes cluster + +* We have created a FIS experiment and ran it to interrupt one of our Spot instances. We watched how the cluster responded using the visual web tool kube-ops-view. + + +{{% notice info %}} +Congratulations ! You have completed the dynamic scaling section of this workshop. +In the next sections we will collect our conclusions and clean up the setup. +{{% /notice %}} diff --git a/content/karpenter/050_scaling/test_hpa.md b/content/karpenter/050_scaling/test_hpa.md index 1844fd52..307d8cdf 100644 --- a/content/karpenter/050_scaling/test_hpa.md +++ b/content/karpenter/050_scaling/test_hpa.md @@ -102,22 +102,3 @@ or kubectl top pods ``` {{% /expand %}} - - -## What Have we learned in this section : - -In this section we have learned: - -* We have built an container image using a multi-stage approach and uploaded the resulting microservice into Amazon Elastic Container Registry (ECR). - -* We have deployed a Monte Carlo Microservice applying all the lessons learned from the previous section. - -* We have set up the Horizontal Pod Autoscaler (HPA) to scale our Monte Carlo microservice whenever the average CPU percentage exceeds 50%, We configured it to scale from 3 replicas to 100 replicas - -* We have sent request to the Monte Carlo microservice to stress the CPU of the Pods where it runs. We saw in action dynamic scaling with HPA and Karpenter and now know can we appy this techniques to our kubernetes cluster - - -{{% notice info %}} -Congratulations ! You have completed the dynamic scaling section of this workshop. -In the next sections we will collect our conclusions and clean up the setup. -{{% /notice %}} \ No newline at end of file diff --git a/content/karpenter/200_cleanup/_index.md b/content/karpenter/200_cleanup/_index.md index 24997f6d..109d7719 100644 --- a/content/karpenter/200_cleanup/_index.md +++ b/content/karpenter/200_cleanup/_index.md @@ -10,6 +10,11 @@ If you're running in an account that was created for you as part of an AWS event If you're running in your own account, make sure you run through these steps to make sure you don't encounter unwanted costs. {{% /notice %}} +## Removing the CloudFormation stack used for FIS +``` +aws cloudformation delete-stack --stack-name $FIS_EXP_NAME +``` + ## Cleaning up HPA, CA, and the Microservice ``` cd ~/environment From c912d43a1ef316e36693c46766709955f67dccc8 Mon Sep 17 00:00:00 2001 From: Robert McCone Date: Thu, 1 Sep 2022 11:07:08 -0700 Subject: [PATCH 02/13] Simplified and clarified the returned messages for running the spot experiment --- content/karpenter/050_scaling/fis_experiment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/karpenter/050_scaling/fis_experiment.md b/content/karpenter/050_scaling/fis_experiment.md index f7be9108..10e37837 100644 --- a/content/karpenter/050_scaling/fis_experiment.md +++ b/content/karpenter/050_scaling/fis_experiment.md @@ -152,7 +152,7 @@ If the experiment completed successfully you should see a response like this: } ``` -If `status` is listed as `running` or `pending`, wait a few seconds and run the command again. If `status` is listed as `failed` or `cancelled` with `reason` as either `Target resolution returned empty set` or `Action cancelled` it means you do not have any Spot instances running with the `intent: apps` label and so no instance was selected for termination. +If `status` is listed as `running`, wait a few seconds and run the command again. If `status` is listed as `failed` with `reason` as `Target resolution returned empty set` it means you do not have any Spot instances running with the `intent: apps` label and so no instance was selected for termination. You can watch how your cluster reacts to the notice with kube-ops-view. Recall you can get the URL for your kube-ops-view by running: From 84001203b1248e53dccd6a7b69d2ee62f417a782 Mon Sep 17 00:00:00 2001 From: Robert McCone Date: Fri, 2 Sep 2022 09:16:13 -0700 Subject: [PATCH 03/13] removed redundant command --- content/karpenter/050_scaling/fis_experiment.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/karpenter/050_scaling/fis_experiment.md b/content/karpenter/050_scaling/fis_experiment.md index 10e37837..fd279a35 100644 --- a/content/karpenter/050_scaling/fis_experiment.md +++ b/content/karpenter/050_scaling/fis_experiment.md @@ -161,9 +161,8 @@ kubectl get svc kube-ops-view | tail -n 1 | awk '{ print "Kube-ops-view URL = ht ``` {{% notice note %}} -You can interrupt more instances by running the experiment multiple times and watch how your cluster reacts, just reissue these commands: +You can interrupt more instances by running the experiment multiple times and watch how your cluster reacts, just reissue this command: ``` -FIS_EXP_TEMP_ID=$(aws cloudformation describe-stacks --stack-name $FIS_EXP_NAME --query "Stacks[0].Outputs[?OutputKey=='FISExperimentID'].OutputValue" --output text) FIS_EXP_ID=$(aws fis start-experiment --experiment-template-id $FIS_EXP_TEMP_ID --no-cli-pager --query "experiment.id" --output text) ``` {{% /notice %}} From 5189a79a5bea180b873d5e2e301a37c5ed098159 Mon Sep 17 00:00:00 2001 From: Carlos Manzanedo Rueda Date: Sun, 4 Sep 2022 18:15:34 -0500 Subject: [PATCH 04/13] Karpenter update to 1.23 versions for eksctl, kubectl and eks cluster --- content/karpenter/010_prerequisites/k8stools.md | 2 +- .../eks-spot-workshop-quickstart-cnf.yml | 17 +++++++++-------- content/karpenter/020_eksctl/launcheks.md | 2 +- content/karpenter/020_eksctl/prerequisites.md | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/content/karpenter/010_prerequisites/k8stools.md b/content/karpenter/010_prerequisites/k8stools.md index 2721ed50..6e5bca8d 100644 --- a/content/karpenter/010_prerequisites/k8stools.md +++ b/content/karpenter/010_prerequisites/k8stools.md @@ -16,7 +16,7 @@ for the download links.](https://docs.aws.amazon.com/eks/latest/userguide/gettin #### Install kubectl ``` -export KUBECTL_VERSION=v1.21.2 +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 ``` diff --git a/content/karpenter/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml b/content/karpenter/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml index 2f413105..90687e1a 100644 --- a/content/karpenter/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml +++ b/content/karpenter/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml @@ -3,7 +3,7 @@ AWSTemplateFormatVersion: '2010-09-09' Description: AWS CloudFormation template to create a Cloud9 environment setup with kubectl, eksctl and an EKS cluster with a managed node group. Please allow ~20min for the EKS cluster to be ready. Metadata: Author: - Description: Sandeep Palavalasa + Description: Carlos Rueda License: Description: 'Copyright 2020 Amazon.com, Inc. and its affiliates. All Rights Reserved. @@ -20,29 +20,30 @@ Parameters: C9InstanceType: Description: Example Cloud9 instance type Type: String - Default: t2.micro + Default: t3.micro AllowedValues: - - t2.micro + - t3.micro + - m5.large ConstraintDescription: Must be a valid Cloud9 instance type C9KubectlVersion: Description: Cloud9 instance kubectl version Type: String - Default: v1.21.2 + Default: v1.23.7 ConstraintDescription: Must be a valid kubectl version C9KubectlVersionTEST: Description: Cloud9 instance kubectl version Type: String - Default: v1.21.2 + Default: v1.23.7 ConstraintDescription: Must be a valid kubectl version C9EKSctlVersion: Description: Cloud9 instance eksctl version Type: String - Default: v0.68.0 + Default: v0.110.0 ConstraintDescription: Must be a valid eksctl version EKSClusterVersion: Description: EKS Cluster Version Type: String - Default: 1.21 + Default: 1.23 ConstraintDescription: Must be a valid eks version EKSClusterName: Description: EKS Cluster Name @@ -160,7 +161,7 @@ Resources: Fn::GetAtt: - C9LambdaExecutionRole - Arn - Runtime: python3.6 + Runtime: python3.9 MemorySize: 256 Timeout: '600' Code: diff --git a/content/karpenter/020_eksctl/launcheks.md b/content/karpenter/020_eksctl/launcheks.md index 0c387cd3..3842e4c6 100644 --- a/content/karpenter/020_eksctl/launcheks.md +++ b/content/karpenter/020_eksctl/launcheks.md @@ -41,7 +41,7 @@ kind: ClusterConfig metadata: name: eksworkshop-eksctl region: ${AWS_REGION} - version: "1.21" + version: "1.23" tags: karpenter.sh/discovery: eksworkshop-eksctl iam: diff --git a/content/karpenter/020_eksctl/prerequisites.md b/content/karpenter/020_eksctl/prerequisites.md index c7f3acb9..de5c47b4 100644 --- a/content/karpenter/020_eksctl/prerequisites.md +++ b/content/karpenter/020_eksctl/prerequisites.md @@ -6,7 +6,7 @@ weight: 10 For this module, we need to download the [eksctl](https://eksctl.io/) binary: ``` -export EKSCTL_VERSION=v0.68.0 +export EKSCTL_VERSION=v0.110.0 curl --silent --location "https://github.com/weaveworks/eksctl/releases/download/${EKSCTL_VERSION}/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp sudo mv -v /tmp/eksctl /usr/local/bin From 0e8bac1ed5fc10be59a05aee4274561107d71fee Mon Sep 17 00:00:00 2001 From: Carlos Manzanedo Rueda Date: Sun, 4 Sep 2022 20:33:36 -0500 Subject: [PATCH 05/13] half-way solving issues with helm and kube-ops-view, opting for kustomization embeded in workshop --- .../eks-spot-workshop-quickstart-cnf.yml | 1 + .../030_k8s_tools/deploy_metric_server.md | 2 +- .../karpenter/030_k8s_tools/helm_deploy.md | 25 +++------ .../kube_ops_view/deployment.yaml | 53 +++++++++++++++++++ .../kube_ops_view/kustomization.yaml | 6 +++ .../k8_tools.files/kube_ops_view/rbac.yaml | 33 ++++++++++++ .../k8_tools.files/kube_ops_view/service.yaml | 16 ++++++ content/karpenter/_index.md | 2 +- 8 files changed, 118 insertions(+), 20 deletions(-) create mode 100644 content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml create mode 100644 content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/kustomization.yaml create mode 100644 content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/rbac.yaml create mode 100644 content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/service.yaml diff --git a/content/karpenter/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml b/content/karpenter/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml index 90687e1a..024f3b35 100644 --- a/content/karpenter/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml +++ b/content/karpenter/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml @@ -282,6 +282,7 @@ Resources: - !Sub sed -i.bak -e 's/--AZB--/${AWS::Region}b/' /home/ec2-user/environment/eksworkshop.yaml - !Sub sed -i.bak -e 's/--EKS_VERSION--/"'"${EKSClusterVersion}"'"/' /home/ec2-user/environment/eksworkshop.yaml - sudo -H -u ec2-user /usr/local/bin/eksctl create cluster -f /home/ec2-user/environment/eksworkshop.yaml + - sudo -H -u ec2-user /usr/local/bin/aws eks update-kubeconfig --name eksworkshop-eksctl - sudo -H -u ec2-user /usr/local/bin/kubectl get nodes C9BootstrapAssociation: diff --git a/content/karpenter/030_k8s_tools/deploy_metric_server.md b/content/karpenter/030_k8s_tools/deploy_metric_server.md index 5b873c13..a59fac11 100644 --- a/content/karpenter/030_k8s_tools/deploy_metric_server.md +++ b/content/karpenter/030_k8s_tools/deploy_metric_server.md @@ -9,7 +9,7 @@ weight: 20 Metrics Server is a scalable, efficient source of container resource metrics for Kubernetes built-in autoscaling pipelines. These metrics will drive the scaling behavior of the [deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/). We will deploy the metrics server using [Kubernetes Metrics Server](https://github.com/kubernetes-sigs/metrics-server). ```sh -kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.0/components.yaml +kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.1/components.yaml ``` Lets' verify the status of the metrics-server `APIService` diff --git a/content/karpenter/030_k8s_tools/helm_deploy.md b/content/karpenter/030_k8s_tools/helm_deploy.md index 6b2cbe90..1ed31fee 100644 --- a/content/karpenter/030_k8s_tools/helm_deploy.md +++ b/content/karpenter/030_k8s_tools/helm_deploy.md @@ -25,31 +25,20 @@ Before we can get started configuring Helm, we'll need to first install the command line tools that you will interact with. To do this, run the following: ``` -export DESIRED_VERSION=v3.8.2 +export DESIRED_VERSION=v3.9.4 curl -sSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash ``` -We can verify the version - -``` -helm version --short -``` - -Let's configure our first Chart repository. Chart repositories are similar to -APT or yum repositories that you might be familiar with on Linux, or Taps for -Homebrew on macOS. +{{% notice note %}} +Note we are using a version relatively old of Helm and the same will apply to the load of the stable repo +This is temporary required to load kube-ops-view. +{{% /notice %}} -Download the `stable` repository so we have something to start with: -``` -helm repo add stable https://charts.helm.sh/stable/ -helm repo update -``` - -Once this is installed, we will be able to list the charts you can install: +We can verify the version ``` -helm search repo stable +helm version --short ``` Finally, let's configure Bash completion for the `helm` command: diff --git a/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml b/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml new file mode 100644 index 00000000..0228ed74 --- /dev/null +++ b/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml @@ -0,0 +1,53 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + application: kube-ops-view + component: frontend + name: kube-ops-view +spec: + replicas: 1 + selector: + matchLabels: + application: kube-ops-view + component: frontend + template: + metadata: + labels: + application: kube-ops-view + component: frontend + spec: + nodeSelector: + intent: control-apps + serviceAccountName: kube-ops-view + containers: + - name: service + image: hjacobs/kube-ops-view:20.4.0 + ports: + - containerPort: 8080 + protocol: TCP + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 5 + timeoutSeconds: 1 + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 5 + resources: + limits: + cpu: 800m + memory: 800Mi + requests: + cpu: 800m + memory: 800Mi + securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 \ No newline at end of file diff --git a/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/kustomization.yaml b/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/kustomization.yaml new file mode 100644 index 00000000..bc60c0b4 --- /dev/null +++ b/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - rbac.yaml + - deployment.yaml + - service.yaml diff --git a/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/rbac.yaml b/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/rbac.yaml new file mode 100644 index 00000000..6e2f2fa5 --- /dev/null +++ b/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/rbac.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kube-ops-view +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: kube-ops-view +rules: +- apiGroups: [""] + resources: ["nodes", "pods"] + verbs: + - list +- apiGroups: ["metrics.k8s.io"] + resources: ["nodes", "pods"] + verbs: + - get + - list +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: kube-ops-view +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kube-ops-view +subjects: +- kind: ServiceAccount + name: kube-ops-view + namespace: default \ No newline at end of file diff --git a/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/service.yaml b/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/service.yaml new file mode 100644 index 00000000..4c6ab902 --- /dev/null +++ b/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + application: kube-ops-view + component: frontend + name: kube-ops-view +spec: + selector: + application: kube-ops-view + component: frontend + type: LoadBalancer + ports: + - port: 80 + protocol: TCP + targetPort: 8080 \ No newline at end of file diff --git a/content/karpenter/_index.md b/content/karpenter/_index.md index 824f5b03..d3d86d9b 100644 --- a/content/karpenter/_index.md +++ b/content/karpenter/_index.md @@ -11,7 +11,7 @@ In this workshop, you will learn how to provision, manage, and maintain your Kub On EKS we will run a small EKS managed node groups, to deploy a minimum set of On-Demand instances that we will use to deploy controllers. After that we will use Karpenter to deploy a mix of On-Demand and Spot instances to showcase a few of the benefits of running a group-less auto scaler. EC2 Spot Instances allow you to architect for optimizations on cost and scale. -This workshop is originally based on AWS [EKS Workshop](https://eksworkshop.com/)but expands and focuses on how efficient Flexible Compute can be implemented using Karpenter. You can find there more modules and learn about other Amazon Elastic Kubernetes Service best practices. +This workshop is originally based on AWS [EKS Workshop](https://eksworkshop.com/) but expands and focuses on how efficient Flexible Compute can be implemented using Karpenter. You can find there more modules and learn about other Amazon Elastic Kubernetes Service best practices. {{% notice note %}} In this workshop we will not cover the introduction to EKS. We expect users of this workshop to understand about Kubernetes, Horizontal Pod Autoscaler and Cluster Autoscaler. Please refer to the **[Containers with EKS](using_ec2_spot_instances_with_eks/005_introduction.html)** workshops From 6295e16e760b6a9ae9ea2b11d29bef52fe8c2493 Mon Sep 17 00:00:00 2001 From: Carlos Manzanedo Rueda Date: Sun, 4 Sep 2022 20:46:42 -0500 Subject: [PATCH 06/13] kube-ops-view updated to latest version of EKS and removed helm installation instructions --- .../030_k8s_tools/install_kube_ops_view.md | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/content/karpenter/030_k8s_tools/install_kube_ops_view.md b/content/karpenter/030_k8s_tools/install_kube_ops_view.md index 1b26231f..5f744786 100644 --- a/content/karpenter/030_k8s_tools/install_kube_ops_view.md +++ b/content/karpenter/030_k8s_tools/install_kube_ops_view.md @@ -5,36 +5,31 @@ weight: 30 --- Now that we have helm installed, we are ready to use the stable helm catalog and install tools -that will help with understanding our cluster setup in a visual way. The first of those tools that we are going to install is [Kube-ops-view](https://github.com/hjacobs/kube-ops-view) from **[Henning Jacobs](https://github.com/hjacobs)**. +In this step we will install [Kube-ops-view](https://github.com/hjacobs/kube-ops-view) from **[Henning Jacobs](https://github.com/hjacobs)**. Kube-ops-view will help with understanding our cluster setup in a visual way -The following line updates the stable helm repository and then installs kube-ops-view using a LoadBalancer Service type and creating a RBAC (Resource Base Access Control) entry for the read-only service account to read nodes and pods information from the cluster. +The following lines download the spec required to deploy kube-ops-view using a LoadBalancer Service type and creating a RBAC (Resource Base Access Control) entry for the read-only service account to read nodes and pods information from the cluster. ``` -helm install kube-ops-view \ -stable/kube-ops-view \ ---set service.type=LoadBalancer \ ---set nodeSelector.intent=control-apps \ ---version 1.2.4 \ ---set rbac.create=True +mkdir $HOME/environment/kube-ops-view; pushd $HOME/environment/kube-ops-view +for file in kustomization.yaml rbac.yaml deployment.yaml service.yaml; do curl https://raw.githubusercontent.com/ruecarlo/ec2-spot-workshops/karpenter-upgrade-0.16.1/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/${file} > ${file}; done +pushd +kubectl apply -k $HOME/environment/kube-ops-view ``` -The execution above installs kube-ops-view exposing it through a Service using the LoadBalancer type. -A successful execution of the command will display the set of resources created and will prompt some advice asking you to use `kubectl proxy` and a local URL for the service. Given we are using the type LoadBalancer for our service, we can disregard this; Instead we will point our browser to the external load balancer. - {{% notice warning %}} -Monitoring and visualization shouldn't be typically be exposed publicly unless the service is properly secured and provide methods for authentication and authorization. You can still deploy kube-ops-view using a Service of type **ClusterIP** by removing the `--set service.type=LoadBalancer` section and using `kubectl proxy`. Kube-ops-view does also [support Oauth 2](https://github.com/hjacobs/kube-ops-view#configuration) +Monitoring and visualization shouldn't be typically be exposed publicly unless the service is properly secured and provide methods for authentication and authorization. You can still deploy kube-ops-view as Service of type **ClusterIP** by removing the `--set service.type=LoadBalancer` section and using `kubectl proxy`. Kube-ops-view does also [support Oauth 2](https://github.com/hjacobs/kube-ops-view#configuration) {{% /notice %}} To check the chart was installed successfully: ``` -helm list +kubectl get svc ``` should display : ``` -NAME NAMESPACE REVISION UPDATED STATUS CHART -kube-ops-view default 1 2020-11-20 05:16:47 deployed kube-ops-view-1.2.4 +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kube-ops-view LoadBalancer 10.100.162.132 addb6e7f91aae4b0dbd6f5833f9750c3-1014347204.eu-west-1.elb.amazonaws.com 80:31628/TCP 3m58s ``` With this we can explore kube-ops-view output by checking the details about the newly service created. From cb36c4be1ac65aba37a4aa5e7e06b5adcf85edf2 Mon Sep 17 00:00:00 2001 From: Carlos Manzanedo Rueda Date: Sun, 4 Sep 2022 20:57:30 -0500 Subject: [PATCH 07/13] kube-ops-view ready and pointing to the master branch --- content/karpenter/030_k8s_tools/install_kube_ops_view.md | 5 ++--- content/karpenter/040_karpenter/_index.md | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/content/karpenter/030_k8s_tools/install_kube_ops_view.md b/content/karpenter/030_k8s_tools/install_kube_ops_view.md index 5f744786..1427479b 100644 --- a/content/karpenter/030_k8s_tools/install_kube_ops_view.md +++ b/content/karpenter/030_k8s_tools/install_kube_ops_view.md @@ -10,9 +10,8 @@ In this step we will install [Kube-ops-view](https://github.com/hjacobs/kube-ops The following lines download the spec required to deploy kube-ops-view using a LoadBalancer Service type and creating a RBAC (Resource Base Access Control) entry for the read-only service account to read nodes and pods information from the cluster. ``` -mkdir $HOME/environment/kube-ops-view; pushd $HOME/environment/kube-ops-view -for file in kustomization.yaml rbac.yaml deployment.yaml service.yaml; do curl https://raw.githubusercontent.com/ruecarlo/ec2-spot-workshops/karpenter-upgrade-0.16.1/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/${file} > ${file}; done -pushd +mkdir $HOME/environment/kube-ops-view +for file in kustomization.yaml rbac.yaml deployment.yaml service.yaml; do curl "https://raw.githubusercontent.com/awslabs/ec2-spot-workshops/master/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/${file}" > $HOME/environment/kube-ops-view/${file}; done kubectl apply -k $HOME/environment/kube-ops-view ``` diff --git a/content/karpenter/040_karpenter/_index.md b/content/karpenter/040_karpenter/_index.md index fad1f29f..f12d1220 100644 --- a/content/karpenter/040_karpenter/_index.md +++ b/content/karpenter/040_karpenter/_index.md @@ -6,6 +6,6 @@ weight: 40 draft: false --- -In this section we will setup Karpenter. Karpenter is an open-source autoscaling project built for Kubernetes. Karpenter is designed to provide the right compute resources to match your application’s needs in seconds, instead of minutes by observing the aggregate resource requests of unschedulable pods and makes decisions to launch and terminate nodes to minimize scheduling latencies. +In this section we will setup Karpenter. Karpenter is an open-source autoscaling project built for Kubernetes. Karpenter is designed to provide the right compute resources to match your application’s needs in seconds, instead of minutes by observing the aggregate resource requests of unschedulable pods and makes decisions to launch and terminate nodes to optimize the cluster cost. ![Karpenter](/images/karpenter/karpenter_banner.png) From aa96a50a68fd74f193f83a67a8b359d0e0150a6a Mon Sep 17 00:00:00 2001 From: Carlos Manzanedo Rueda Date: Sun, 4 Sep 2022 21:31:11 -0500 Subject: [PATCH 08/13] updating karpenter version and setup --- .../k8_tools.files/kube_ops_view/deployment.yaml | 8 ++++---- .../karpenter/040_karpenter/install_karpenter.md | 14 ++++++++++---- .../040_karpenter/set_up_the_environment.md | 2 +- .../040_karpenter/set_up_the_provisioner.md | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml b/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml index 0228ed74..6fc8f9ae 100644 --- a/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml +++ b/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml @@ -42,11 +42,11 @@ spec: failureThreshold: 5 resources: limits: - cpu: 800m - memory: 800Mi + cpu: 400m + memory: 400Mi requests: - cpu: 800m - memory: 800Mi + cpu: 400m + memory: 400Mi securityContext: readOnlyRootFilesystem: true runAsNonRoot: true diff --git a/content/karpenter/040_karpenter/install_karpenter.md b/content/karpenter/040_karpenter/install_karpenter.md index 57fc409e..b176e19e 100644 --- a/content/karpenter/040_karpenter/install_karpenter.md +++ b/content/karpenter/040_karpenter/install_karpenter.md @@ -49,13 +49,19 @@ To check running pods run the command below. There should be at least one pod `k kubectl get pods --namespace karpenter ``` +You should see an output similar to the one below. +``` +NAME READY STATUS RESTARTS AGE +karpenter-6c59fc5c96-l8fwv 2/2 Running 0 3m51s +karpenter-6c59fc5c96-zw5jf 2/2 Running 0 3m51s +``` + + To check the deployment. Like with the pods, there should be one deployment `karpenter` ``` kubectl get deployment -n karpenter ``` -{{% notice note %}} -You can increase the number of Karpenter replicas in the deployment for resilience. Karpenter will elect a leader controller that in charge of running operations. +{{% notice info %}} +Since **v0.16.0** Karpenter deploys 2 replicas. One of the replicas is elected as as a Leader while the other stays in standby mode. Karpenter deployment also uses `topologySpreadConstraints` to spread each replica in a different AZ. {{% /notice %}} - - diff --git a/content/karpenter/040_karpenter/set_up_the_environment.md b/content/karpenter/040_karpenter/set_up_the_environment.md index 9d9ec419..9a7f35ee 100644 --- a/content/karpenter/040_karpenter/set_up_the_environment.md +++ b/content/karpenter/040_karpenter/set_up_the_environment.md @@ -12,7 +12,7 @@ Before we install Karpenter, there are a few things that we will need to prepare Instances launched by Karpenter must run with an InstanceProfile that grants permissions necessary to run containers and configure networking. Karpenter discovers the InstanceProfile using the name `KarpenterNodeRole-${ClusterName}`. ``` -export KARPENTER_VERSION=v0.13.1 +export KARPENTER_VERSION=v0.16.1 echo "export KARPENTER_VERSION=${KARPENTER_VERSION}" >> ~/.bash_profile TEMPOUT=$(mktemp) curl -fsSL https://karpenter.sh/"${KARPENTER_VERSION}"/getting-started/getting-started-with-eksctl/cloudformation.yaml > $TEMPOUT \ diff --git a/content/karpenter/040_karpenter/set_up_the_provisioner.md b/content/karpenter/040_karpenter/set_up_the_provisioner.md index e5d20340..9b01c00c 100644 --- a/content/karpenter/040_karpenter/set_up_the_provisioner.md +++ b/content/karpenter/040_karpenter/set_up_the_provisioner.md @@ -75,7 +75,7 @@ The configuration for the provider is split into two parts. The first one define {{% notice info %}} -Karpenter has been designed to be generic and support other Cloud and Infrastructure providers. At the moment of writing this workshop (**Karpenter 0.13.1**) main implementation and Provisioner available is on AWS. You can read more about the **[configuration available for the AWS Provisioner here](https://karpenter.sh/v0.13.1/aws/)** +Karpenter has been designed to be generic and support other Cloud and Infrastructure providers. At the moment of writing this workshop (**Karpenter 0.16.1**) main implementation and Provisioner available is on AWS. You can read more about the **[configuration available for the AWS Provisioner here](https://karpenter.sh/v0.16.1/aws/)**. {{% /notice %}} ## Displaying Karpenter Logs From c57fc7fae48e0db6cc9533e4cf2636fb9d524a78 Mon Sep 17 00:00:00 2001 From: Carlos Manzanedo Rueda Date: Sun, 4 Sep 2022 21:50:16 -0500 Subject: [PATCH 09/13] updated the node provisioning section --- .../automatic_node_provisioning.md | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/content/karpenter/040_karpenter/automatic_node_provisioning.md b/content/karpenter/040_karpenter/automatic_node_provisioning.md index 945b8a13..066cb4f9 100644 --- a/content/karpenter/040_karpenter/automatic_node_provisioning.md +++ b/content/karpenter/040_karpenter/automatic_node_provisioning.md @@ -96,24 +96,25 @@ echo type: $(kubectl describe node --selector=intent=apps | grep "beta.kubernete There is something even more interesting to learn about how the node was provisioned. Check out Karpenter logs and look at the new Karpenter created. The lines should be similar to the ones below ```bash -2022-07-01T03:00:19.634Z INFO controller.provisioning Found 1 provisionable pod(s) {"commit": "1f7a67b"} -2022-07-01T03:00:19.634Z INFO controller.provisioning Computed 1 new node(s) will fit 1 pod(s) {"commit": "1f7a67b"} -2022-07-01T03:00:19.790Z DEBUG controller.provisioning.cloudprovider Discovered subnets: [subnet-0e528fbbaf13542c2 (eu-west-1b) subnet-0a9bd9b668d8ae58d (eu-west-1a) subnet-03aec03eee186dc42 (eu-west-1a) subnet-03ff683f2535bcd8d (eu-west-1b)] {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:00:19.871Z DEBUG controller.provisioning.cloudprovider Discovered security groups: [sg-076f0ca74b68addb2 sg-09176f21ae53f5d60] {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:00:19.873Z DEBUG controller.provisioning.cloudprovider Discovered kubernetes version 1.21 {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:00:19.928Z DEBUG controller.provisioning.cloudprovider Discovered ami-0413b176c68479e84 for query "/aws/service/eks/optimized-ami/1.21/amazon-linux-2/recommended/image_id" {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:00:19.972Z DEBUG controller.provisioning.cloudprovider Discovered launch template Karpenter-eksworkshop-eksctl-12663282710833670681 {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:00:23.013Z INFO controller.provisioning.cloudprovider Launched instance: i-05e8535378b1caf35, hostname: ip-192-168-36-234.eu-west-1.compute.internal, type: c5a.xlarge, zone: eu-west-1b, capacityType: spot {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:00:23.042Z INFO controller.provisioning Created node with 1 pods requesting {"cpu":"1125m","memory":"1536Mi","pods":"3"} from types t3a.xlarge, c6a.xlarge, c5a.xlarge, t3.xlarge, c6i.xlarge and 333 other(s) {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:00:23.042Z INFO controller.provisioning Waiting for unschedulable pods {"commit": "1f7a67b"} -2022-07-01T03:00:23.042Z DEBUG controller.events Normal {"commit": "1f7a67b", "object": {"kind":"Pod","namespace":"default","name":"inflate-b9d769f59-rcjnj","uid":"e0f98d1d-eaf6-46ff-9ea0-4d66a6842815","apiVersion":"v1","resourceVersion":"20925"}, "reason": "NominatePod", "message": "Pod should schedule on ip-192-168-36-234.eu-west-1.compute.internal"} +2022-09-05T02:23:43.907Z DEBUG controller.consolidation Discovered 542 EC2 instance types {"commit": "b157d45"} +2022-09-05T02:23:44.070Z DEBUG controller.consolidation Discovered subnets: [subnet-085b9778ddacc06bb (eu-west-1b) subnet-0c6313dad0015b677 (eu-west-1a) subnet-02b72b91f674af299 (eu-west-1a) subnet-0471989b1d4fe9e0a (eu-west-1b)] {"commit": "b157d45"} +2022-09-05T02:23:44.188Z DEBUG controller.consolidation Discovered EC2 instance types zonal offerings for subnets {"alpha.eksctl.io/cluster-name":"eksworkshop-eksctl"} {"commit": "b157d45"} +2022-09-05T02:33:19.634Z DEBUG controller.provisioning 27 out of 509 instance types were excluded because they would breach provisioner limits {"commit": "b157d45"} +2022-09-05T02:33:19.640Z INFO controller.provisioning Found 1 provisionable pod(s) {"commit": "b157d45"} +2022-09-05T02:33:19.640Z INFO controller.provisioning Computed 1 new node(s) will fit 1 pod(s) {"commit": "b157d45"} +2022-09-05T02:33:19.648Z INFO controller.provisioning Launching node with 1 pods requesting {"cpu":"1125m","memory":"1536Mi","pods":"3"} from types t3a.xlarge, t3.xlarge, c6i.xlarge, c6id.xlarge, c6a.xlarge and 332 other(s) {"commit": "b157d45", "provisioner": "default"} +2022-09-05T02:33:19.742Z DEBUG controller.provisioning.cloudprovider Discovered security groups: [sg-06f776cc53ed4b025 sg-0bb5f95986167d336] {"commit": "b157d45", "provisioner": "default"} +2022-09-05T02:33:19.744Z DEBUG controller.provisioning.cloudprovider Discovered kubernetes version 1.23 {"commit": "b157d45", "provisioner": "default"} +2022-09-05T02:33:19.810Z DEBUG controller.provisioning.cloudprovider Discovered ami-044d355a56926f0c6 for query "/aws/service/eks/optimized-ami/1.23/amazon-linux-2/recommended/image_id" {"commit": "b157d45", "provisioner": "default"} +2022-09-05T02:33:19.981Z DEBUG controller.provisioning.cloudprovider Created launch template, Karpenter-eksworkshop-eksctl-6351194516503745500 {"commit": "b157d45", "provisioner": "default"} +2022-09-05T02:33:22.282Z INFO controller.provisioning.cloudprovider Launched instance: i-091de07c985bd851e, hostname: ip-192-168-61-204.eu-west-1.compute.internal, type: c5.xlarge, zone: eu-west-1b, capacityType: spot {"commit": "b157d45", "provisioner": "default"} ``` We explained earlier on about group-less cluster scalers and how that simplifies operations and maintenance. Let's deep dive for a second into this concept. Notice how Karpenter picks up the instance from a diversified selection of instances. In this case it selected the following instances: ``` -from types t3a.xlarge, c6a.xlarge, c5a.xlarge, t3.xlarge, c6i.xlarge and 333 other(s) +Launching node with 1 pods requesting {"cpu":"1125m","memory":"1536Mi","pods":"3"} from types t3a.xlarge, t3.xlarge, c6i.xlarge, c6id.xlarge, c6a.xlarge and 332 other(s) ``` **Note** how the types, 'nano', 'micro', 'small', 'medium', 'large', where filtered for this selection. While our recommendation is to diversify on as many instances as possible, there are cases where provisioners may want to filter smaller (or specific) instances types. @@ -123,7 +124,7 @@ Instances types might be different depending on the region selected. All this instances are the suitable instances that reduce the waste of resources (memory and CPU) for the pod submitted. If you are interested in Algorithms, internally Karpenter is using a [First Fit Decreasing (FFD)](https://en.wikipedia.org/wiki/Bin_packing_problem#First_Fit_Decreasing_(FFD)) approach. Note however this can change in the future. -We did set Karpenter Provisioner to use [EC2 Spot instances](https://aws.amazon.com/ec2/spot/), and there was no `instance-types` [requirement section in the Provisioner to filter the type of instances](https://karpenter.sh/v0.10.0/provisioner/#instance-types). This means that Karpenter will use the default value of instances types to use. The default value includes all instance types with the exclusion of metal (non-virtualized), [non-HVM](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/virtualization_types.html), and GPU instances.Internally Karpenter used **EC2 Fleet in Instant mode** to provision the instances. You can read more about EC2 Fleet Instant mode [**here**](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instant-fleet.html). Here are a few properties to mention about EC2 Fleet instant mode that are key for Karpenter. +We did set Karpenter Provisioner to use [EC2 Spot instances](https://aws.amazon.com/ec2/spot/), and there was no `instance-types` [requirement section in the Provisioner to filter the type of instances](https://karpenter.sh/v0.16.1/provisioner/#instance-types). This means that Karpenter will use the default value of instances types to use. The default value includes all instance types with the exclusion of metal (non-virtualized), [non-HVM](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/virtualization_types.html), and GPU instances.Internally Karpenter used **EC2 Fleet in Instant mode** to provision the instances. You can read more about EC2 Fleet Instant mode [**here**](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instant-fleet.html). Here are a few properties to mention about EC2 Fleet instant mode that are key for Karpenter. * EC2 Fleet instant mode provides a synchronous call to procure instances, including EC2 Spot, this simplifies and avoid error when provisioning instances. For those of you familiar with [Cluster Autoscaler on AWS](https://github.com/kubernetes/autoscaler/blob/c4b56ea56136681e8a8ff654dfcd813c0d459442/cluster-autoscaler/cloudprovider/aws/auto_scaling_groups.go#L33-L36), you may know about how it uses `i-placeholder` to coordinate instances that have been created in asynchronous ways. @@ -153,7 +154,7 @@ Let's now focus in a few of those parameters starting with the Labels: Labels: ... intent=apps karpenter.sh/capacity-type=spot - node.kubernetes.io/instance-type=t3.medium + node.kubernetes.io/instance-type=c5.xlarge topology.kubernetes.io/region=eu-west-1 topology.kubernetes.io/zone=eu-west-1a karpenter.sh/provisioner-name=default @@ -170,9 +171,11 @@ Another thing to note from the node description is the following section: ```bash System Info: ... + OS Image: Amazon Linux 2 Operating System: linux Architecture: amd64 - Container Runtime Version: containerd://1.4.6 + Container Runtime Version: containerd://1.6.6 + Kubelet Version: v1.23.9-eks-ba74326 ... ``` @@ -229,22 +232,18 @@ This will set a few pods pending. Karpenter will get the pending pod signal and ```bash -2022-07-01T03:13:32.754Z INFO controller.provisioning Found 7 provisionable pod(s) {"commit": "1f7a67b"} -2022-07-01T03:13:32.754Z INFO controller.provisioning Computed 1 new node(s) will fit 7 pod(s) {"commit": "1f7a67b"} -2022-07-01T03:13:32.824Z DEBUG controller.provisioning.cloudprovider Discovered subnets: [subnet-0e528fbbaf13542c2 (eu-west-1b) subnet-0a9bd9b668d8ae58d (eu-west-1a) subnet-03aec03eee186dc42 (eu-west-1a) subnet-03ff683f2535bcd8d (eu-west-1b)] {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:13:32.867Z DEBUG controller.provisioning.cloudprovider Discovered security groups: [sg-076f0ca74b68addb2 sg-09176f21ae53f5d60] {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:13:32.868Z DEBUG controller.provisioning.cloudprovider Discovered kubernetes version 1.21 {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:13:32.929Z DEBUG controller.provisioning.cloudprovider Discovered ami-0413b176c68479e84 for query "/aws/service/eks/optimized-ami/1.21/amazon-linux-2/recommended/image_id" {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:13:33.105Z DEBUG controller.provisioning.cloudprovider Created launch template, Karpenter-eksworkshop-eksctl-12663282710833670681 {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:13:35.047Z INFO controller.provisioning.cloudprovider Launched instance: i-004d9de653118ae9d, hostname: ip-192-168-27-254.eu-west-1.compute.internal, type: t3a.2xlarge, zone: eu-west-1a, capacityType: spot {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:13:35.074Z INFO controller.provisioning Created node with 7 pods requesting {"cpu":"7125m","memory":"10752Mi","pods":"9"} from types t3a.2xlarge, c6a.2xlarge, c5a.2xlarge, t3.2xlarge, c6i.2xlarge and 276 other(s) {"commit": "1f7a67b", "provisioner": "default"} -2022-07-01T03:13:35.074Z INFO controller.provisioning Waiting for unschedulable pods {"commit": "1f7a67b"} +2022-09-05T02:40:15.714Z DEBUG controller.provisioning 27 out of 509 instance types were excluded because they would breach provisioner limits {"commit": "b157d45"} +2022-09-05T02:40:15.769Z INFO controller.provisioning Found 7 provisionable pod(s) {"commit": "b157d45"} +2022-09-05T02:40:15.769Z INFO controller.provisioning Computed 1 new node(s) will fit 7 pod(s) {"commit": "b157d45"} +2022-09-05T02:40:15.784Z INFO controller.provisioning Launching node with 7 pods requesting {"cpu":"7125m","memory":"10752Mi","pods":"9"} from types inf1.2xlarge, c3.2xlarge, r3.2xlarge, c5a.2xlarge, t3a.2xlarge and 280 other(s) {"commit": "b157d45", "provisioner": "default"} +2022-09-05T02:40:16.111Z DEBUG controller.provisioning.cloudprovider Created launch template, Karpenter-eksworkshop-eksctl-6351194516503745500 {"commit": "b157d45", "provisioner": "default"} +2022-09-05T02:40:18.115Z INFO controller.provisioning.cloudprovider Launched instance: i-0081fc25504eacc93, hostname: ip-192-168-16-71.eu-west-1.compute.internal, type: c5a.2xlarge, zone: eu-west-1a, capacityType: spot {"commit": "b157d45", "provisioner": "default"} ``` Indeed the instances selected this time are larger ! The instances selected in this example were: ```bash -from types t3a.2xlarge, c6a.2xlarge, c5a.2xlarge, t3.2xlarge, c6i.2xlarge and 276 other(s) +Launching node with 7 pods requesting {"cpu":"7125m","memory":"10752Mi","pods":"9"} from types inf1.2xlarge, c3.2xlarge, r3.2xlarge, c5a.2xlarge, t3a.2xlarge and 280 other(s) ``` @@ -273,15 +272,18 @@ Let's cover the second reason why we started with 0 replicas and why we also end {{% /expand %}} -## What Have we learned in this section : +## What Have we learned in this section: In this section we have learned: * Karpenter scales up nodes in a group-less approach. Karpenter select which nodes to scale , based on the number of pending pods and the *Provisioner* configuration. It selects how the best instances for the workload should look like, and then provisions those instances. This is unlike what Cluster Autoscaler does. In the case of Cluster Autoscaler, first all existing node group are evaluated and to find which one is the best placed to scale, given the Pod constraints. -* Karpenter uses cordon and drain [best practices](https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/) to terminate nodes. The configuration of when a node is terminated can be controlled with `ttlSecondsAfterEmpty` - * Karpenter can scale-out from zero when applications have available working pods and scale-in to zero when there are no running jobs or pods. * Provisioners can be setup to define governance and rules that define how nodes will be provisioned within a cluster partition. We can setup requirements such as `karpenter.sh/capacity-type` to allow on-demand and spot instances or use `karpenter.k8s.aws/instance-size` to filter smaller sizes. The full list of supported labels is available **[here](https://karpenter.sh/v0.13.1/tasks/scheduling/#selecting-nodes)** +* Karpenter uses cordon and drain [best practices](https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/) to terminate nodes. The configuration of when a node is terminated can be controlled with `ttlSecondsAfterEmpty` + + +The ability to terminate nodes only when they are completely idle is ideal for clusters or provisioners used by **batch** workloads. This is controlled by the setting `ttlSecondsAfterEmpty`. In batch workloads you want to ideally let all the kubernetes `jobs` to complete and for a node to be idle before removing the node. This behaviour is not ideal in scenarios where the workload are long running stateless micro-services. Under this conditions the best approach is to use Karpenter **consolidation** functionality. Let's explore how consolidation works in the next section. + From c42024b2255d97e4ef05124d8258d4f698976600 Mon Sep 17 00:00:00 2001 From: Carlos Manzanedo Rueda Date: Mon, 5 Sep 2022 06:05:21 -0500 Subject: [PATCH 10/13] added consolidation section --- .../040_karpenter/advanced_provisioner.md | 12 +- .../karpenter/040_karpenter/consolidation.md | 282 ++++++++++++++++++ .../040_karpenter/ec2_spot_deployments.md | 4 +- .../040_karpenter/multiple_architectures.md | 40 +-- .../using_alternative_provisioners.md | 2 +- 5 files changed, 310 insertions(+), 30 deletions(-) create mode 100644 content/karpenter/040_karpenter/consolidation.md diff --git a/content/karpenter/040_karpenter/advanced_provisioner.md b/content/karpenter/040_karpenter/advanced_provisioner.md index fba8ddc1..b89a6b2d 100644 --- a/content/karpenter/040_karpenter/advanced_provisioner.md +++ b/content/karpenter/040_karpenter/advanced_provisioner.md @@ -1,7 +1,7 @@ --- title: "Deploying Multiple Provisioners" date: 2021-11-07T11:05:19-07:00 -weight: 50 +weight: 60 draft: false --- @@ -28,6 +28,9 @@ kind: Provisioner metadata: name: default spec: + consolidation: + enabled: true + weight: 100 labels: intent: apps requirements: @@ -41,7 +44,6 @@ spec: resources: cpu: 1000 memory: 1000Gi - ttlSecondsAfterEmpty: 30 ttlSecondsUntilExpired: 2592000 providerRef: name: default @@ -71,6 +73,8 @@ kind: Provisioner metadata: name: team1 spec: + consolidation: + enabled: true labels: intent: apps requirements: @@ -84,7 +88,6 @@ spec: resources: cpu: 1000 memory: 1000Gi - ttlSecondsAfterEmpty: 30 ttlSecondsUntilExpired: 2592000 taints: - effect: NoSchedule @@ -126,6 +129,9 @@ Let's spend some time covering a few points in the Provisioners configuration. * The `team1` Provisioner does define a different `AWSNodeTemplate` and changes the AMI from the default [EKS optimized AMI](https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami.html) to [bottlerocket](https://aws.amazon.com/bottlerocket/). It does also adapts the UserData bootstrapping for this particular provider. +* The `default` Provisioner is setting up a weight of 100. The evaluation of provisioners can use weights, this is useful to force scenarios where you want karpenter to evaluate a provisioner before other. The higher the weightthe higher the priority in the evaluation. The first provisioner to match the workload is the one that gets used. + + {{% notice note %}} If Karpenter encounters a taint in the Provisioner that is not tolerated by a Pod, Karpenter won’t use that Provisioner to provision the pod. It is recommended to create Provisioners that are mutually exclusive. So no Pod should match multiple Provisioners. If multiple Provisioners are matched, Karpenter will randomly choose which to use. {{% /notice %}} diff --git a/content/karpenter/040_karpenter/consolidation.md b/content/karpenter/040_karpenter/consolidation.md new file mode 100644 index 00000000..04dbda0f --- /dev/null +++ b/content/karpenter/040_karpenter/consolidation.md @@ -0,0 +1,282 @@ +--- +title: "Consolidation" +date: 2021-11-07T11:05:19-07:00 +weight: 50 +draft: false +--- + +In the previous section we did set the default provisioner configured with a specific `ttlSecondsAfterEmpty`. This instructs Karpenter to remove nodes after `ttlSecondsAfterEmpty` of a node being empty. Note Karpenter will take Daemonset into consideration.We also know that nodes can be removed when they reach the `ttlSecondsUntilExpired`. This is ideal to force node termination on the cluster while bringing new nodes that will pick up the latest AMI's. + +{{% notice note %}} +Automated deprovisioning is configured through the ProvisionerSpec `.ttlSecondsAfterEmpty`, `.ttlSecondsUntilExpired` and `.consolidation.enabled` fields. If these are not configured, Karpenter will not default values for them and will not terminate nodes. +{{% /notice %}} + +There is another way to configure Karpenter to deprovision nodes called **Consolidation**. This mode is preferred for workloads such as microservices and is imcompatible with setting up the `ttlSecondsAfterEmpty` . When set in consolidation mode Karpenter works to actively reduce cluster cost by identifying when nodes can be removed as their workloads will run on other nodes in the cluster and when nodes can be replaced with cheaper variants due to a change in the workloads. + +Before we proceed to see how Consolidation works, let's change the default provisioner configuration: +``` +cat < Date: Mon, 5 Sep 2022 06:13:48 -0500 Subject: [PATCH 11/13] updated the multiple provisioner section --- .../040_karpenter/advanced_provisioner.md | 3 +-- .../using_alternative_provisioners.md | 25 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/content/karpenter/040_karpenter/advanced_provisioner.md b/content/karpenter/040_karpenter/advanced_provisioner.md index b89a6b2d..3e643145 100644 --- a/content/karpenter/040_karpenter/advanced_provisioner.md +++ b/content/karpenter/040_karpenter/advanced_provisioner.md @@ -73,8 +73,6 @@ kind: Provisioner metadata: name: team1 spec: - consolidation: - enabled: true labels: intent: apps requirements: @@ -88,6 +86,7 @@ spec: resources: cpu: 1000 memory: 1000Gi + ttlSecondsAfterEmpty: 30 ttlSecondsUntilExpired: 2592000 taints: - effect: NoSchedule diff --git a/content/karpenter/040_karpenter/using_alternative_provisioners.md b/content/karpenter/040_karpenter/using_alternative_provisioners.md index 119b1c43..c291ecd3 100644 --- a/content/karpenter/040_karpenter/using_alternative_provisioners.md +++ b/content/karpenter/040_karpenter/using_alternative_provisioners.md @@ -132,19 +132,18 @@ The output of Karpenter should look similar to the one below ``` ... -2022-07-01T04:12:15.781Z INFO controller.provisioning Found 4 provisionable pod(s) {"commit": "1f7a67b"} -2022-07-01T04:12:15.781Z INFO controller.provisioning Computed 2 new node(s) will fit 4 pod(s) {"commit": "1f7a67b"} -2022-07-01T04:12:15.967Z DEBUG controller.provisioning.cloudprovider Discovered subnets: [subnet-0e528fbbaf13542c2 (eu-west-1b) subnet-0a9bd9b668d8ae58d (eu-west-1a) subnet-03aec03eee186dc42 (eu-west-1a) subnet-03ff683f2535bcd8d (eu-west-1b)] {"commit": "1f7a67b", "provisioner": "team1"} -2022-07-01T04:12:16.063Z DEBUG controller.provisioning.cloudprovider Discovered security groups: [sg-076f0ca74b68addb2 sg-09176f21ae53f5d60] {"commit": "1f7a67b", "provisioner": "team1"} -2022-07-01T04:12:16.071Z DEBUG controller.provisioning.cloudprovider Discovered kubernetes version 1.21 {"commit": "1f7a67b", "provisioner": "team1"} -2022-07-01T04:12:16.179Z DEBUG controller.provisioning.cloudprovider Discovered ami-015933fe34749f648 for query "/aws/service/bottlerocket/aws-k8s-1.21/x86_64/latest/image_id" {"commit": "1f7a67b", "provisioner": "team1"} -2022-07-01T04:12:16.456Z DEBUG controller.provisioning.cloudprovider Created launch template, Karpenter-eksworkshop-eksctl-641081096202606695 {"commit": "1f7a67b", "provisioner": "team1"} -2022-07-01T04:12:17.277Z DEBUG controller.node-state Discovered 531 EC2 instance types {"commit": "1f7a67b", "node": "ip-192-168-25-60.eu-west-1.compute.internal"} -2022-07-01T04:12:17.418Z DEBUG controller.node-state Discovered EC2 instance types zonal offerings {"commit": "1f7a67b", "node": "ip-192-168-25-60.eu-west-1.compute.internal"} -2022-07-01T04:12:18.287Z INFO controller.provisioning.cloudprovider Launched instance: i-0e81a84185e589749, hostname: ip-192-168-37-210.eu-west-1.compute.internal, type: t3a.xlarge, zone: eu-west-1b, capacityType: on-demand {"commit": "1f7a67b", "provisioner": "team1"} -2022-07-01T04:12:18.302Z INFO controller.provisioning.cloudprovider Launched instance: i-03c9fc74527b401f4, hostname: ip-192-168-7-134.eu-west-1.compute.internal, type: t3a.xlarge, zone: eu-west-1a, capacityType: on-demand {"commit": "1f7a67b", "provisioner": "team1"} -2022-07-01T04:12:18.306Z INFO controller.provisioning Created node with 2 pods requesting {"cpu":"2125m","memory":"512M","pods":"4"} from types t3a.xlarge, c6a.xlarge, c5a.xlarge, c6i.xlarge, t3.xlarge and 315 other(s) {"commit": "1f7a67b", "provisioner": "team1"} -2022-07-01T04:12:18.306Z DEBUG controller.events Normal {"commit": "1f7a67b", "object": {"kind":"Pod","namespace":"default","name":"inflate-team1-865b77c748-dp9k5","uid":"5b682809-1ae9-4ed2-85c9-451abc11cf75","apiVersion":"v1","resourceVersion":"43463"}, "reason": "NominatePod", "message": "Pod should schedule on ip-192-168-37-210.eu-west-1.compute.internal"} +2022-09-05T11:11:33.993Z DEBUG controller.provisioning 27 out of 509 instance types were excluded because they would breach provisioner limits {"commit": "b157d45"} +2022-09-05T11:11:33.993Z DEBUG controller.provisioning 27 out of 509 instance types were excluded because they would breach provisioner limits {"commit": "b157d45"} +2022-09-05T11:11:33.999Z DEBUG controller.provisioning 27 out of 509 instance types were excluded because they would breach provisioner limits {"commit": "b157d45"} +2022-09-05T11:11:33.999Z DEBUG controller.provisioning 381 out of 509 instance types were excluded because they would breach provisioner limits {"commit": "b157d45"} +2022-09-05T11:11:34.006Z INFO controller.provisioning Found 4 provisionable pod(s) {"commit": "b157d45"} +2022-09-05T11:11:34.006Z INFO controller.provisioning Computed 2 new node(s) will fit 4 pod(s) {"commit": "b157d45"} +2022-09-05T11:11:34.007Z INFO controller.provisioning Launching node with 2 pods requesting {"cpu":"2125m","memory":"512M","pods":"4"} from types t3a.xlarge, c6a.xlarge, c5a.xlarge, t3.xlarge, c6i.xlarge and 35 other(s) {"commit": "b157d45", "provisioner": "team1"} +2022-09-05T11:11:34.014Z INFO controller.provisioning Launching node with 2 pods requesting {"cpu":"2125m","memory":"512M","pods":"4"} from types t3a.xlarge, c6a.xlarge, c5a.xlarge, t3.xlarge, c6i.xlarge and 325 other(s) {"commit": "b157d45", "provisioner": "team1"} +2022-09-05T11:11:34.342Z DEBUG controller.provisioning.cloudprovider Discovered launch template Karpenter-eksworkshop-eksctl-14752700009555043417 {"commit": "b157d45", "provisioner": "team1"} +2022-09-05T11:11:36.601Z DEBUG controller.provisioning.cloudprovider InsufficientInstanceCapacity for offering { instanceType: t3a.xlarge, zone: eu-west-1b, capacityType: on-demand }, avoiding for 3m0s {"commit": "b157d45", "provisioner": "team1"} +2022-09-05T11:11:36.748Z INFO controller.provisioning.cloudprovider Launched instance: i-0b44228e7195f7588, hostname: ip-192-168-42-207.eu-west-1.compute.internal, type: c6a.xlarge, zone: eu-west-1b, capacityType: on-demand {"commit": "b157d45", "provisioner": "team1"} +2022-09-05T11:11:38.400Z INFO controller.provisioning.cloudprovider Launched instance: i-0e5173a4f48019515, hostname: ip-192-168-31-229.eu-west-1.compute.internal, type: t3a.xlarge, zone: eu-west-1a, capacityType: on-demand {"commit": "b157d45", "provisioner": "team1"} ... ``` From bcd2c4cbe51ee6eb564cc7c3e593ad27846bc258 Mon Sep 17 00:00:00 2001 From: Carlos Manzanedo Rueda Date: Mon, 5 Sep 2022 06:32:50 -0500 Subject: [PATCH 12/13] workshop updated and ready to be tested --- content/karpenter/050_scaling/fis_experiment.md | 4 +++- content/karpenter/200_cleanup/_index.md | 4 ++-- content/karpenter/300_conclusion/conclusion.md | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/content/karpenter/050_scaling/fis_experiment.md b/content/karpenter/050_scaling/fis_experiment.md index fd279a35..d7f22aed 100644 --- a/content/karpenter/050_scaling/fis_experiment.md +++ b/content/karpenter/050_scaling/fis_experiment.md @@ -4,6 +4,8 @@ date: 2022-08-31T13:12:00-07:00 weight: 50 --- +During this workshop we have been making extensive use of Spot instances. One question users of Spot instances ask is how they can reproduce the effects of an instance termination so they can qualify if an application would have degradation or issues when spot instances are terminated and replaced by other instances from pools where capacity is available. + In this section, you're going to create and run an experiment to [trigger the interruption of Amazon EC2 Spot Instances using AWS Fault Injection Simulator (FIS)](https://aws.amazon.com/blogs/compute/implementing-interruption-tolerance-in-amazon-ec2-spot-with-aws-fault-injection-simulator/). When using Spot Instances, you need to be prepared to be interrupted. With FIS, you can test the resiliency of your workload and validate that your application is reacting to the interruption notices that EC2 sends before terminating your instances. You can target individual Spot Instances or a subset of instances in clusters managed by services that tag your instances such as ASG, EC2 Fleet, and EKS. #### What do you need to get started? @@ -25,7 +27,7 @@ Parameters: DurationBeforeInterruption: Description: Number of minutes before the interruption - Default: 2 + Default: 3 Type: Number Resources: diff --git a/content/karpenter/200_cleanup/_index.md b/content/karpenter/200_cleanup/_index.md index 109d7719..d610f7e5 100644 --- a/content/karpenter/200_cleanup/_index.md +++ b/content/karpenter/200_cleanup/_index.md @@ -27,8 +27,8 @@ kubectl delete -f inflate-spot.yaml kubectl delete -f inflate.yaml helm uninstall aws-node-termination-handler --namespace kube-system helm uninstall karpenter -n karpenter -helm uninstall kube-ops-view -kubectl delete -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.0/components.yaml +kubectl delete -k $HOME/environment/kube-ops-view +kubectl delete -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.1/components.yaml ``` ## Removing the cluster, Managed node groups and Karpenter pre-requisites diff --git a/content/karpenter/300_conclusion/conclusion.md b/content/karpenter/300_conclusion/conclusion.md index df8ae168..4f4ff5ab 100644 --- a/content/karpenter/300_conclusion/conclusion.md +++ b/content/karpenter/300_conclusion/conclusion.md @@ -14,6 +14,7 @@ In the session, we have: - We have learned how Karpenter support custom AMI's and bootsrapping. - We learned how Karpenter uses well-known labels and acts on them procuring capacity that meets criterias such as which architecture to use, which type of instances (On-Demand or Spot) to use. - We learned how Karpenter applies best practices for large scale deployment by diversifying and using allocation strategies for both on demand instances and EC2 Spot instances, we also learned applications have still full control and can set Node Selectors such as `node.kubernetes.io/instance-type: m5.2xlarge` or `topology.kubernetes.io/zone=us-east-1c` to specify explicitely what instance type to use or which AZ an application must be deployed in. +- Learned how deprovisioning works in Karpenter and how to set up the Cluster Consolidation option. - Configured a DaemonSet using **AWS-Node-Termination-Handler** to handle spot interruptions gracefully. We also learned that in future version the integration with the termination controller will be proactive in handling Spot Terminations and Rebalance recommendations. # EC2 Spot Savings From aca3fe1b0478b5441ce2490ce5d2eb9028f8a716 Mon Sep 17 00:00:00 2001 From: Carlos Manzanedo Rueda Date: Thu, 8 Sep 2022 04:46:29 +0100 Subject: [PATCH 13/13] Karpenter update tested and reduced to just focus on Karpenter through the cloudformation stack on event engine and regular accounts --- .../010_prerequisites/attach_workspaceiam.md | 15 --- .../karpenter/010_prerequisites/aws_event.md | 30 ++---- content/karpenter/010_prerequisites/awscli.md | 28 ------ .../karpenter/010_prerequisites/k8stools.md | 58 ----------- .../karpenter/010_prerequisites/self_paced.md | 55 +++++++++- content/karpenter/010_prerequisites/sshkey.md | 25 ----- .../010_prerequisites/update_workspaceiam.md | 52 ---------- .../karpenter/010_prerequisites/workspace.md | 43 -------- content/karpenter/020_eksctl/_index.md | 13 --- .../create_eks_cluster_eksctl_command.md | 58 ----------- content/karpenter/020_eksctl/launcheks.md | 95 ------------------ content/karpenter/020_eksctl/prerequisites.md | 18 ---- .../_index.md | 2 +- .../deploy_metric_server.md | 0 .../helm_deploy.md | 0 .../install_kube_ops_view.md | 0 .../kube_ops_view/deployment.yaml | 0 .../kube_ops_view/kustomization.yaml | 0 .../k8_tools.files/kube_ops_view/rbac.yaml | 0 .../k8_tools.files/kube_ops_view/service.yaml | 0 .../_index.md | 2 +- .../advanced_provisioner.md | 0 .../automatic_node_provisioning.md | 0 .../consolidation.md | 3 +- .../ec2_spot_deployments.md | 0 .../install_karpenter.md | 0 .../multiple_architectures.md | 6 +- .../set_up_the_environment.md | 0 .../set_up_the_provisioner.md | 10 +- .../using_alternative_provisioners.md | 3 +- .../{050_scaling => 060_scaling}/_index.md | 2 +- .../build_and_push_to_ecr.md | 0 .../deploy_hpa.md | 0 .../fis_experiment.md | 0 .../monte_carlo_pi.md | 3 +- .../{050_scaling => 060_scaling}/test_hpa.md | 0 .../{020_eksctl => }/console_credentials.md | 2 +- content/karpenter/{020_eksctl => }/test.md | 2 +- .../prerequisites/cfn_stak_completion.png | Bin 0 -> 82156 bytes 39 files changed, 82 insertions(+), 443 deletions(-) delete mode 100644 content/karpenter/010_prerequisites/attach_workspaceiam.md delete mode 100644 content/karpenter/010_prerequisites/awscli.md delete mode 100644 content/karpenter/010_prerequisites/k8stools.md delete mode 100644 content/karpenter/010_prerequisites/sshkey.md delete mode 100644 content/karpenter/010_prerequisites/update_workspaceiam.md delete mode 100644 content/karpenter/010_prerequisites/workspace.md delete mode 100644 content/karpenter/020_eksctl/_index.md delete mode 100644 content/karpenter/020_eksctl/create_eks_cluster_eksctl_command.md delete mode 100644 content/karpenter/020_eksctl/launcheks.md delete mode 100644 content/karpenter/020_eksctl/prerequisites.md rename content/karpenter/{030_k8s_tools => 040_k8s_tools}/_index.md (96%) rename content/karpenter/{030_k8s_tools => 040_k8s_tools}/deploy_metric_server.md (100%) rename content/karpenter/{030_k8s_tools => 040_k8s_tools}/helm_deploy.md (100%) rename content/karpenter/{030_k8s_tools => 040_k8s_tools}/install_kube_ops_view.md (100%) rename content/karpenter/{030_k8s_tools => 040_k8s_tools}/k8_tools.files/kube_ops_view/deployment.yaml (100%) rename content/karpenter/{030_k8s_tools => 040_k8s_tools}/k8_tools.files/kube_ops_view/kustomization.yaml (100%) rename content/karpenter/{030_k8s_tools => 040_k8s_tools}/k8_tools.files/kube_ops_view/rbac.yaml (100%) rename content/karpenter/{030_k8s_tools => 040_k8s_tools}/k8_tools.files/kube_ops_view/service.yaml (100%) rename content/karpenter/{040_karpenter => 050_karpenter}/_index.md (97%) rename content/karpenter/{040_karpenter => 050_karpenter}/advanced_provisioner.md (100%) rename content/karpenter/{040_karpenter => 050_karpenter}/automatic_node_provisioning.md (100%) rename content/karpenter/{040_karpenter => 050_karpenter}/consolidation.md (98%) rename content/karpenter/{040_karpenter => 050_karpenter}/ec2_spot_deployments.md (100%) rename content/karpenter/{040_karpenter => 050_karpenter}/install_karpenter.md (100%) rename content/karpenter/{040_karpenter => 050_karpenter}/multiple_architectures.md (96%) rename content/karpenter/{040_karpenter => 050_karpenter}/set_up_the_environment.md (100%) rename content/karpenter/{040_karpenter => 050_karpenter}/set_up_the_provisioner.md (90%) rename content/karpenter/{040_karpenter => 050_karpenter}/using_alternative_provisioners.md (97%) rename content/karpenter/{050_scaling => 060_scaling}/_index.md (99%) rename content/karpenter/{050_scaling => 060_scaling}/build_and_push_to_ecr.md (100%) rename content/karpenter/{050_scaling => 060_scaling}/deploy_hpa.md (100%) rename content/karpenter/{050_scaling => 060_scaling}/fis_experiment.md (100%) rename content/karpenter/{050_scaling => 060_scaling}/monte_carlo_pi.md (95%) rename content/karpenter/{050_scaling => 060_scaling}/test_hpa.md (100%) rename content/karpenter/{020_eksctl => }/console_credentials.md (99%) rename content/karpenter/{020_eksctl => }/test.md (99%) create mode 100644 static/images/karpenter/prerequisites/cfn_stak_completion.png diff --git a/content/karpenter/010_prerequisites/attach_workspaceiam.md b/content/karpenter/010_prerequisites/attach_workspaceiam.md deleted file mode 100644 index a206d197..00000000 --- a/content/karpenter/010_prerequisites/attach_workspaceiam.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: "Attach the IAM role to your Workspace" -chapter: false -weight: 50 ---- - -{{% notice note %}} -**Select the tab** and follow the specific instructions depending on whether you are… -{{% /notice %}} - - -{{< tabs name="Region" >}} - {{< tab name="...ON YOUR OWN" include="on_your_own_updateiam.md" />}} - {{< tab name="...AT AN AWS EVENT" include="at_an_aws_updateiam.md" />}} -{{< /tabs >}} \ No newline at end of file diff --git a/content/karpenter/010_prerequisites/aws_event.md b/content/karpenter/010_prerequisites/aws_event.md index 94cdbca0..f342f489 100644 --- a/content/karpenter/010_prerequisites/aws_event.md +++ b/content/karpenter/010_prerequisites/aws_event.md @@ -24,36 +24,20 @@ If you are at an AWS event, an AWS account was created for you to use throughout 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. {{% notice info %}} -In the interest of time for shorter events we sometimes deploy the resources required as a prerequisite for you. If you were told so, please review the cloudformation outputs of the stack that was deployed by **expanding the instructions below**. +In the interest of time we have deployed everything required to run Karpenter for this workshop. All the pre-requisites and dependencies have been deployed. The resources deployed can befound in this CloudFormation Template (**[eks-spot-workshop-quickstarter-cnf.yml](https://raw.githubusercontent.com/awslabs/ec2-spot-workshops/master/content/using_ec2_spot_instances_with_eks/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml)**). The template deploys resourcess such as (a) An [AWS Cloud9](https://console.aws.amazon.com/cloud9) workspace with all the dependencies and IAM privileges to run the workshop (b) An EKS Cluster with the name `eksworkshop-eksctl` and (c) a [EKS managed node group](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html) with 2 on-demand instances. {{% /notice %}} -{{%expand "Click to reveal detailed instructions" %}} +#### Getting access to Cloud9 -#### What resources are already deployed {#resources_deployed} - -We have deployed the below resources required to get started with the workshop using a CloudFormation Template (**[eks-spot-workshop-quickstarter-cnf.yml](https://raw.githubusercontent.com/awslabs/ec2-spot-workshops/master/content/using_ec2_spot_instances_with_eks/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml)**), Please reference the below resources created by the stack. - -+ An [AWS Cloud9](https://console.aws.amazon.com/cloud9) workspace with - - An IAM role created and attached to the workspace with Administrator access - - Kubernetes tools installed (kubectl, jq and envsubst) - - awscli upgraded to v2 - - Created and imported a key pair to Amazon EC2 - - [eksctl](https://eksctl.io/) installed, The official CLI for Amazon EKS - -+ An EKS cluster with the name `eksworkshop-eksctl` and a [EKS managed node group](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html) with 2 on-demand instances. - - -#### Use your resources - -In this workshop, you'll need to reference the resources created by the CloudFormation stack that we setup for you. +In this workshop, you'll need to reference the resources created by the CloudFormation stack. 1. On the [AWS CloudFormation console](https://console.aws.amazon.com/cloudformation), select the stack name that starts with **mod-** in the list. -1. In the stack details pane, click the **Outputs** tab. +2. In the stack details pane, click the **Outputs** tab. ![cnf_output](/images/karpenter/prerequisites/cnf_output.png) -It is recommended that you keep this window open so you can easily refer to the outputs and resources throughout the workshop. +It is recommended that you keep this tab / window open so you can easily refer to the outputs and resources throughout the workshop. {{% notice info %}} you will notice additional Cloudformation stacks were also deployed which is the result of the stack that starts with **mod-**. One to deploy the Cloud9 Workspace and two other to create the EKS cluster and managed nodegroup. @@ -78,9 +62,7 @@ aws sts get-caller-identity {{% insert-md-from-file file="karpenter/010_prerequisites/at_an_aws_validaterole.md" %}} -Since we have already setup the prerequisites, **you can head straight to [Test the Cluster]({{< relref "/karpenter/020_eksctl/test.md" >}})** - -{{% /expand%}} +You are now ready to **[Test the Cluster]({{< relref "/karpenter/test.md" >}})** diff --git a/content/karpenter/010_prerequisites/awscli.md b/content/karpenter/010_prerequisites/awscli.md deleted file mode 100644 index 3aeb060e..00000000 --- a/content/karpenter/010_prerequisites/awscli.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: "Update to the latest AWS CLI" -chapter: false -weight: 45 -comment: default install now includes aws-cli/1.15.83 ---- - -{{% notice tip %}} -For this workshop, please ignore warnings about the version of pip being used. -{{% /notice %}} - -1. Run the following command to view the current version of aws-cli: -``` -aws --version -``` - -1. Update to the latest version: -``` -curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" -unzip awscliv2.zip -sudo ./aws/install -. ~/.bash_profile -``` - -1. Confirm you have a newer version: -``` -aws --version -``` diff --git a/content/karpenter/010_prerequisites/k8stools.md b/content/karpenter/010_prerequisites/k8stools.md deleted file mode 100644 index 6e5bca8d..00000000 --- a/content/karpenter/010_prerequisites/k8stools.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: "Install Kubernetes Tools" -chapter: false -weight: 40 ---- - -Amazon EKS clusters require kubectl and kubelet binaries and the aws-cli or aws-iam-authenticator -binary to allow IAM authentication for your Kubernetes cluster. - -{{% notice tip %}} -In this workshop we will give you the commands to download the Linux -binaries. If you are running Mac OSX / Windows, please [see the official EKS docs -for the download links.](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html) -{{% /notice %}} - -#### Install kubectl - -``` -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 -``` - -#### Enable Kubectl bash_completion - -``` -kubectl completion bash >> ~/.bash_completion -. /etc/profile.d/bash_completion.sh -. ~/.bash_completion -``` - -#### Set the AWS Load Balancer Controller version - -``` -echo 'export LBC_VERSION="v2.3.0"' >> ~/.bash_profile -. ~/.bash_profile -``` - -#### Install JQ and envsubst -``` -sudo yum -y install jq gettext bash-completion moreutils -``` - -#### Installing YQ for Yaml processing - -``` -echo 'yq() { - docker run --rm -i -v "${PWD}":/workdir mikefarah/yq "$@" -}' | tee -a ~/.bashrc && source ~/.bashrc -``` - -#### Verify the binaries are in the path and executable -``` -for command in kubectl jq envsubst - do - which $command &>/dev/null && echo "$command in path" || echo "$command NOT FOUND" - done -``` \ No newline at end of file diff --git a/content/karpenter/010_prerequisites/self_paced.md b/content/karpenter/010_prerequisites/self_paced.md index e5d5ce68..bb1194b3 100644 --- a/content/karpenter/010_prerequisites/self_paced.md +++ b/content/karpenter/010_prerequisites/self_paced.md @@ -8,7 +8,10 @@ weight: 10 Only complete this section if you are running the workshop on your own. If you are at an AWS hosted event (such as re:Invent, Kubecon, Immersion Day, etc), go to [Start the workshop at an AWS event]({{< ref "/karpenter/010_prerequisites/aws_event.md" >}}). {{% /notice %}} -### Running the workshop on your own +## Running the workshop on your own + + +### Creating an account to run the workshop {{% notice warning %}} Your account must have the ability to create new IAM roles and scope other IAM permissions. @@ -33,5 +36,53 @@ as an IAM user with administrator access to the AWS account: 1. Take note of the login URL and save: ![Login URL](/images/karpenter/prerequisites/iam-4-save-url.png) +### Deploying CloudFormation + +In the interest of time and to focus just on karpenter, we will install everything required to run this Karpenter workshop using cloudformation. + +1. Download locally this cloudformation stack into a file (**[eks-spot-workshop-quickstarter-cnf.yml](https://raw.githubusercontent.com/awslabs/ec2-spot-workshops/master/content/using_ec2_spot_instances_with_eks/010_prerequisites/prerequisites.files/eks-spot-workshop-quickstart-cnf.yml)**). + +1. Go into the CloudFormation console and select the creation of a new stack. Select **Template is ready**, and then **Upload a template file**, then select the file that you downloaded to your computer and click on **Next** + +1. Fill in the **Stack Name** using 'karpenter-workshop', Leave all the settings in the parameters section with the default prarameters and click **Next** + +1. In the Configure Stack options just scroll to the bottom of the page and click **Next** + +1. Finally in the **Review karpenter-workshop** go to the bottom of the page and tick the `Capabilities` section *I acknowledge that AWS CloudFormation might create IAM resources.* then click **Create stack** + +{{% notice warning %}} +The deployment of this stack may take up to 20minutes. You should wait until all the resources in the cloudformation stack have been completed before you start the rest of the workshop. The template deploys resourcess such as (a) An [AWS Cloud9](https://console.aws.amazon.com/cloud9) workspace with all the dependencies and IAM privileges to run the workshop (b) An EKS Cluster with the name `eksworkshop-eksctl` and (c) a [EKS managed node group](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html) with 2 on-demand instances. +{{% /notice %}} + +### Checking the completion of the stack deployment + +One way to check your stack has been fully deployed is to check that all the cloudformation dependencies are green and succedded in the cloudformation dashboard; This should look similar to the state below. + +![cnf_output](/images/karpenter/prerequisites/cfn_stak_completion.png) + +#### Getting access to Cloud9 + +In this workshop, you'll need to reference the resources created by the CloudFormation stack. + +1. On the [AWS CloudFormation console](https://console.aws.amazon.com/cloudformation), select the stack name that starts with **mod-** in the list. + +2. In the stack details pane, click the **Outputs** tab. + +![cnf_output](/images/karpenter/prerequisites/cnf_output.png) + +It is recommended that you keep this tab / window open so you can easily refer to the outputs and resources throughout the workshop. + +{{% notice info %}} +you will notice additional Cloudformation stacks were also deployed which is the result of the stack that starts with **mod-**. One to deploy the Cloud9 Workspace and two other to create the EKS cluster and managed nodegroup. +{{% /notice %}} + +#### Launch your Cloud9 workspace + +- Click on the url against `Cloud9IDE` from the outputs + +{{% insert-md-from-file file="karpenter/010_prerequisites/workspace_at_launch.md" %}} + +{{% insert-md-from-file file="karpenter/010_prerequisites/update_workspace_settings.md" %}} + -Once you have completed the step above, **you can head straight to [Create a Workspace]({{< ref "/karpenter/010_prerequisites/workspace.md" >}})** \ No newline at end of file +You are now ready to **[Test the Cluster]({{< relref "/karpenter/test.md" >}})** \ No newline at end of file diff --git a/content/karpenter/010_prerequisites/sshkey.md b/content/karpenter/010_prerequisites/sshkey.md deleted file mode 100644 index 2878c344..00000000 --- a/content/karpenter/010_prerequisites/sshkey.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: "Create an SSH key" -chapter: false -weight: 80 ---- - -{{% notice info %}} -Starting from here, when you see command to be entered such as below, you will enter these commands into Cloud9 IDE. You can use the **Copy to clipboard** feature (right hand upper corner) to simply copy and paste into Cloud9. In order to paste, you can use Ctrl + V for Windows or Command + V for Mac. -{{% /notice %}} - -Please run this command to generate SSH Key in Cloud9. This key will be used on the worker node instances to allow ssh access if necessary. - -``` -ssh-keygen -``` - -{{% notice tip %}} -Press `enter` 3 times to take the default choices -{{% /notice %}} - -Upload the public key to your EC2 region: - -``` -aws ec2 import-key-pair --key-name "eksworkshop" --public-key-material fileb://~/.ssh/id_rsa.pub -``` diff --git a/content/karpenter/010_prerequisites/update_workspaceiam.md b/content/karpenter/010_prerequisites/update_workspaceiam.md deleted file mode 100644 index 2ff11a27..00000000 --- a/content/karpenter/010_prerequisites/update_workspaceiam.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: "Update IAM settings for your Workspace" -chapter: false -weight: 60 ---- - -{{% notice info %}} -**Note**: Cloud9 normally manages IAM credentials dynamically. This isn't currently compatible with the EKS IAM authentication, so we will disable it and rely on the IAM role instead. -{{% /notice %}} - -- Return to your workspace and click the sprocket, or launch a new tab to open the Preferences tab -- Select **AWS SETTINGS** -- Turn off **AWS managed temporary credentials** -- Close the Preferences tab -![c9disableiam](/images/karpenter/prerequisites/c9disableiam.png) - -To ensure temporary credentials aren't already in place we will also remove -any existing credentials file: -``` -rm -vf ${HOME}/.aws/credentials -``` - -We should configure our aws cli with our current region as default: -``` -export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account) -export AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region') - -echo "export ACCOUNT_ID=${ACCOUNT_ID}" >> ~/.bash_profile -echo "export AWS_REGION=${AWS_REGION}" >> ~/.bash_profile -aws configure set default.region ${AWS_REGION} -aws configure get default.region -``` - -### Validate the IAM role {#validate_iam} - -Use the [GetCallerIdentity](https://docs.aws.amazon.com/cli/latest/reference/sts/get-caller-identity.html) CLI command to validate that the Cloud9 IDE is using the correct IAM role. - -``` -aws sts get-caller-identity - -``` - -{{% notice note %}} -**Select the tab** and validate the assumed role… -{{% /notice %}} - -{{< tabs name="Region" >}} - {{< tab name="...AT AN AWS EVENT" include="at_an_aws_validaterole.md" />}} - {{< tab name="...ON YOUR OWN" include="on_your_own_validaterole.md" />}} - -{{< /tabs >}} - diff --git a/content/karpenter/010_prerequisites/workspace.md b/content/karpenter/010_prerequisites/workspace.md deleted file mode 100644 index 8c64c21a..00000000 --- a/content/karpenter/010_prerequisites/workspace.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: "Create a Workspace" -chapter: false -weight: 30 ---- - -{{% notice warning %}} -If you are running the workshop on your own, the Cloud9 workspace should be built by an IAM user with Administrator privileges, not the root account user. Please ensure you are logged in as an IAM user, not the root -account user. -{{% /notice %}} - -{{% notice info %}} -If you are at an AWS hosted event (such as re:Invent, Kubecon, Immersion Day, or any other event hosted by -an AWS employee) follow the instructions on the region that should be used to launch resources -{{% /notice %}} - -{{% notice tip %}} -Ad blockers, javascript disablers, and tracking blockers should be disabled for -the cloud9 domain, or connecting to the workspace might be impacted. -Cloud9 requires third-party-cookies. You can whitelist the [specific domains]( https://docs.aws.amazon.com/cloud9/latest/user-guide/troubleshooting.html#troubleshooting-env-loading). -{{% /notice %}} - -### Launch Cloud9 in your closest region: - -{{< tabs name="Region" >}} - {{< tab name="N. Virginia" include="us-east-1.md" />}} - {{< tab name="Oregon" include="us-west-2.md" />}} - {{< tab name="Ireland" include="eu-west-1.md" />}} - {{< tab name="Ohio" include="us-east-2.md" />}} - {{< tab name="Singapore" include="ap-southeast-1.md" />}} -{{< /tabs >}} - -- Select **Create environment** -- Name it **eksworkshop**, and take all other defaults -- When it comes up, customize the environment by closing the **welcome tab** -and **lower work area**, and opening a new **terminal** tab in the main work area: -![c9before](/images/using_ec2_spot_instances_with_eks/prerequisites/c9before.png) - -- Your workspace should now look like this: -![c9after](/images/using_ec2_spot_instances_with_eks/prerequisites/c9after.png) - -- If you like this theme, you can choose it yourself by selecting **View / Themes / Solarized / Solarized Dark** -in the Cloud9 workspace menu. diff --git a/content/karpenter/020_eksctl/_index.md b/content/karpenter/020_eksctl/_index.md deleted file mode 100644 index 2b8cac9f..00000000 --- a/content/karpenter/020_eksctl/_index.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: "Launch using eksctl" -chapter: true -weight: 20 ---- - -# Launch using [eksctl](https://eksctl.io/) - -[eksctl](https://eksctl.io) is the official CLI for Amazon EKS. It is written in Go, and uses CloudFormation. Eksctl is a tool jointly developed by AWS and [Weaveworks](https://weave.works) that automates much of the experience of creating EKS clusters. - -In this module, we will use eksctl to launch and configure our EKS cluster and nodes. - -{{< youtube jGrdVSlIkNQ >}} diff --git a/content/karpenter/020_eksctl/create_eks_cluster_eksctl_command.md b/content/karpenter/020_eksctl/create_eks_cluster_eksctl_command.md deleted file mode 100644 index a3576d43..00000000 --- a/content/karpenter/020_eksctl/create_eks_cluster_eksctl_command.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: "Create EKS cluster Command" -chapter: false -disableToc: true -hidden: true ---- - -Create an eksctl deployment file (eksworkshop.yaml) to create an EKS cluster: - - -``` -cat << EOF > eksworkshop.yaml ---- -apiVersion: eksctl.io/v1alpha5 -kind: ClusterConfig - -metadata: - name: eksworkshop-eksctl - region: ${AWS_REGION} - version: "1.21" - tags: - karpenter.sh/discovery: ${CLUSTER_NAME} -iam: - withOIDC: true -managedNodeGroups: -- amiFamily: AmazonLinux2 - instanceType: m5.large - name: mng-od-m5large - desiredCapacity: 2 - maxSize: 3 - minSize: 0 - labels: - alpha.eksctl.io/cluster-name: ${CLUSTER_NAME} - alpha.eksctl.io/nodegroup-name: mng-od-m5large - intent: control-apps - tags: - alpha.eksctl.io/nodegroup-name: mng-od-m5large - alpha.eksctl.io/nodegroup-type: managed - k8s.io/cluster-autoscaler/node-template/label/intent: control-apps - iam: - withAddonPolicies: - autoScaler: true - cloudWatch: true - albIngress: true - privateNetworking: true - -EOF -``` - -Next, use the file you created as the input for the eksctl cluster creation. - -``` -eksctl create cluster -f eksworkshop.yaml -``` - -{{% notice note %}} -Launching EKS and all the dependencies will take approximately 15 minutes -{{% /notice %}} \ No newline at end of file diff --git a/content/karpenter/020_eksctl/launcheks.md b/content/karpenter/020_eksctl/launcheks.md deleted file mode 100644 index 3842e4c6..00000000 --- a/content/karpenter/020_eksctl/launcheks.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -title: "Launch EKS" -date: 2018-08-07T13:34:24-07:00 -weight: 20 ---- - - -{{% notice warning %}} -**DO NOT PROCEED** with this step unless you have [validated the IAM role]({{< relref "../010_prerequisites/update_workspaceiam.md#validate_iam" >}}) in use by the Cloud9 IDE. You will not be able to run the necessary kubectl commands in the later modules unless the EKS cluster is built using the IAM role. -{{% /notice %}} - -#### Challenge: -**How do I check the IAM role on the workspace?** - -{{%expand "Expand here to see the solution" %}} - -### Validate the IAM role {#validate_iam} - -Use the [GetCallerIdentity](https://docs.aws.amazon.com/cli/latest/reference/sts/get-caller-identity.html) CLI command to validate that the Cloud9 IDE is using the correct IAM role. - -``` -aws sts get-caller-identity - -``` - -You can verify what the output an correct role shoulld be in the **[validate the IAM role section]({{< relref "../010_prerequisites/update_workspaceiam.md" >}})**. If you do see the correct role, proceed to next step to create an EKS cluster. -{{% /expand %}} - - -### Create an EKS cluster - -Create an eksctl deployment file (eksworkshop.yaml) to create an EKS cluster: - - -``` -cat << EOF > eksworkshop.yaml ---- -apiVersion: eksctl.io/v1alpha5 -kind: ClusterConfig - -metadata: - name: eksworkshop-eksctl - region: ${AWS_REGION} - version: "1.23" - tags: - karpenter.sh/discovery: eksworkshop-eksctl -iam: - withOIDC: true -managedNodeGroups: -- amiFamily: AmazonLinux2 - instanceType: m5.large - name: mng-od-m5large - desiredCapacity: 2 - maxSize: 3 - minSize: 0 - labels: - alpha.eksctl.io/cluster-name: eksworkshop-eksctl - alpha.eksctl.io/nodegroup-name: mng-od-m5large - intent: control-apps - tags: - alpha.eksctl.io/nodegroup-name: mng-od-m5large - alpha.eksctl.io/nodegroup-type: managed - k8s.io/cluster-autoscaler/node-template/label/intent: control-apps - iam: - withAddonPolicies: - autoScaler: true - cloudWatch: true - albIngress: true - privateNetworking: true - -EOF -``` - -Next, use the file you created as the input for the eksctl cluster creation. - -``` -eksctl create cluster -f eksworkshop.yaml -``` - -{{% notice info %}} -Launching EKS and all the dependencies will take approximately 15 minutes -{{% /notice %}} - -`eksctl create cluster` command allows you to create the cluster and managed nodegroups in sequence. There are a few things to note in the configuration that we just used to create the cluster and a managed nodegroup. - - * Resources created by `eksctl` have the tag `karpenter.sh/discovery` with the cluster name as the value. We'll need this later. - * Nodegroup configurations are set under the **managedNodeGroups** section, this indicates that the node group is managed by EKS. - * Nodegroup instance type is **m5.large** with **minSize** to 0, **maxSize** to 3 and **desiredCapacity** to 2. This nodegroup has capacity type set to On-Demand Instances by default. - - * Notice that the we add 3 node labels: - * **alpha.eksctl.io/cluster-name**, to indicate the nodes belong to **eksworkshop-eksctl** cluster. - * **alpha.eksctl.io/nodegroup-name**, to indicate the nodes belong to **mng-od-m5large** nodegroup. - * **intent**, to allow you to deploy control applications on nodes that have been labeled with value **control-apps** - - * Amazon EKS adds an additional Kubernetes label **eks.amazonaws.com/capacityType: ON_DEMAND**, to all On-Demand Instances in your managed node group. You can use this label to schedule stateful applications on On-Demand nodes. \ No newline at end of file diff --git a/content/karpenter/020_eksctl/prerequisites.md b/content/karpenter/020_eksctl/prerequisites.md deleted file mode 100644 index de5c47b4..00000000 --- a/content/karpenter/020_eksctl/prerequisites.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: "Prerequisites" -date: 2018-08-07T13:31:55-07:00 -weight: 10 ---- - -For this module, we need to download the [eksctl](https://eksctl.io/) binary: -``` -export EKSCTL_VERSION=v0.110.0 -curl --silent --location "https://github.com/weaveworks/eksctl/releases/download/${EKSCTL_VERSION}/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp - -sudo mv -v /tmp/eksctl /usr/local/bin -``` - -Confirm the eksctl command works: -``` -eksctl version -``` diff --git a/content/karpenter/030_k8s_tools/_index.md b/content/karpenter/040_k8s_tools/_index.md similarity index 96% rename from content/karpenter/030_k8s_tools/_index.md rename to content/karpenter/040_k8s_tools/_index.md index fe2618ad..51d29dcd 100644 --- a/content/karpenter/030_k8s_tools/_index.md +++ b/content/karpenter/040_k8s_tools/_index.md @@ -1,7 +1,7 @@ --- title: "Install Kubernetes Tools" chapter: true -weight: 30 +weight: 40 --- # Install Kubernetes tools diff --git a/content/karpenter/030_k8s_tools/deploy_metric_server.md b/content/karpenter/040_k8s_tools/deploy_metric_server.md similarity index 100% rename from content/karpenter/030_k8s_tools/deploy_metric_server.md rename to content/karpenter/040_k8s_tools/deploy_metric_server.md diff --git a/content/karpenter/030_k8s_tools/helm_deploy.md b/content/karpenter/040_k8s_tools/helm_deploy.md similarity index 100% rename from content/karpenter/030_k8s_tools/helm_deploy.md rename to content/karpenter/040_k8s_tools/helm_deploy.md diff --git a/content/karpenter/030_k8s_tools/install_kube_ops_view.md b/content/karpenter/040_k8s_tools/install_kube_ops_view.md similarity index 100% rename from content/karpenter/030_k8s_tools/install_kube_ops_view.md rename to content/karpenter/040_k8s_tools/install_kube_ops_view.md diff --git a/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml b/content/karpenter/040_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml similarity index 100% rename from content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml rename to content/karpenter/040_k8s_tools/k8_tools.files/kube_ops_view/deployment.yaml diff --git a/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/kustomization.yaml b/content/karpenter/040_k8s_tools/k8_tools.files/kube_ops_view/kustomization.yaml similarity index 100% rename from content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/kustomization.yaml rename to content/karpenter/040_k8s_tools/k8_tools.files/kube_ops_view/kustomization.yaml diff --git a/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/rbac.yaml b/content/karpenter/040_k8s_tools/k8_tools.files/kube_ops_view/rbac.yaml similarity index 100% rename from content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/rbac.yaml rename to content/karpenter/040_k8s_tools/k8_tools.files/kube_ops_view/rbac.yaml diff --git a/content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/service.yaml b/content/karpenter/040_k8s_tools/k8_tools.files/kube_ops_view/service.yaml similarity index 100% rename from content/karpenter/030_k8s_tools/k8_tools.files/kube_ops_view/service.yaml rename to content/karpenter/040_k8s_tools/k8_tools.files/kube_ops_view/service.yaml diff --git a/content/karpenter/040_karpenter/_index.md b/content/karpenter/050_karpenter/_index.md similarity index 97% rename from content/karpenter/040_karpenter/_index.md rename to content/karpenter/050_karpenter/_index.md index f12d1220..235a5403 100644 --- a/content/karpenter/040_karpenter/_index.md +++ b/content/karpenter/050_karpenter/_index.md @@ -2,7 +2,7 @@ title: "Karpenter" titleMenu: "Karpenter" chapter: true -weight: 40 +weight: 50 draft: false --- diff --git a/content/karpenter/040_karpenter/advanced_provisioner.md b/content/karpenter/050_karpenter/advanced_provisioner.md similarity index 100% rename from content/karpenter/040_karpenter/advanced_provisioner.md rename to content/karpenter/050_karpenter/advanced_provisioner.md diff --git a/content/karpenter/040_karpenter/automatic_node_provisioning.md b/content/karpenter/050_karpenter/automatic_node_provisioning.md similarity index 100% rename from content/karpenter/040_karpenter/automatic_node_provisioning.md rename to content/karpenter/050_karpenter/automatic_node_provisioning.md diff --git a/content/karpenter/040_karpenter/consolidation.md b/content/karpenter/050_karpenter/consolidation.md similarity index 98% rename from content/karpenter/040_karpenter/consolidation.md rename to content/karpenter/050_karpenter/consolidation.md index 04dbda0f..7d05303c 100644 --- a/content/karpenter/040_karpenter/consolidation.md +++ b/content/karpenter/050_karpenter/consolidation.md @@ -92,7 +92,8 @@ kubectl scale deployment inflate --replicas 6 As for what should happen, check out karpenter logs, remember you can read karpenter logs using the following command ``` -kubectl logs -f deployment/karpenter -n karpenter -c controller --tail=20 +alias kl='for pod in $(kubectl get pods -n karpenter | grep karpenter | awk NF=1) ; do if [[ $(kubectl logs ${pod} -c controller -n karpenter --limit-bytes=4096) =~ .*acquired.* ]]; then kubectl logs ${pod} -c controller -n karpenter -f --tail=20; fi; done' +kl ``` Karpenter logs will display the following lines diff --git a/content/karpenter/040_karpenter/ec2_spot_deployments.md b/content/karpenter/050_karpenter/ec2_spot_deployments.md similarity index 100% rename from content/karpenter/040_karpenter/ec2_spot_deployments.md rename to content/karpenter/050_karpenter/ec2_spot_deployments.md diff --git a/content/karpenter/040_karpenter/install_karpenter.md b/content/karpenter/050_karpenter/install_karpenter.md similarity index 100% rename from content/karpenter/040_karpenter/install_karpenter.md rename to content/karpenter/050_karpenter/install_karpenter.md diff --git a/content/karpenter/040_karpenter/multiple_architectures.md b/content/karpenter/050_karpenter/multiple_architectures.md similarity index 96% rename from content/karpenter/040_karpenter/multiple_architectures.md rename to content/karpenter/050_karpenter/multiple_architectures.md index 099628d6..6cc48f44 100644 --- a/content/karpenter/040_karpenter/multiple_architectures.md +++ b/content/karpenter/050_karpenter/multiple_architectures.md @@ -118,7 +118,8 @@ Before we check the selected node, let's cover what Karpenter is expected to do Let's confirm that was the case and only `amd64` considered for scaling up. We can check karpenter logs by running the following command. ``` -kubectl logs -f deployment/karpenter -c controller -n karpenter +alias kl='for pod in $(kubectl get pods -n karpenter | grep karpenter | awk NF=1) ; do if [[ $(kubectl logs ${pod} -c controller -n karpenter --limit-bytes=4096) =~ .*acquired.* ]]; then kubectl logs ${pod} -c controller -n karpenter -f --tail=20; fi; done' +kl ``` The output should show something similar to the lines below @@ -194,7 +195,8 @@ Karpenter does support the nodeSelector well-known label `node.kubernetes.io/ins So in this case we should expect just one instance being considered. You can check Karpenter logs by running: ``` -kubectl logs -f deployment/karpenter -c controller -n karpenter +alias kl='for pod in $(kubectl get pods -n karpenter | grep karpenter | awk NF=1) ; do if [[ $(kubectl logs ${pod} -c controller -n karpenter --limit-bytes=4096) =~ .*acquired.* ]]; then kubectl logs ${pod} -c controller -n karpenter -f --tail=20; fi; done' +kl ``` The output should show something similar to the lines below diff --git a/content/karpenter/040_karpenter/set_up_the_environment.md b/content/karpenter/050_karpenter/set_up_the_environment.md similarity index 100% rename from content/karpenter/040_karpenter/set_up_the_environment.md rename to content/karpenter/050_karpenter/set_up_the_environment.md diff --git a/content/karpenter/040_karpenter/set_up_the_provisioner.md b/content/karpenter/050_karpenter/set_up_the_provisioner.md similarity index 90% rename from content/karpenter/040_karpenter/set_up_the_provisioner.md rename to content/karpenter/050_karpenter/set_up_the_provisioner.md index 9b01c00c..9acb50fd 100644 --- a/content/karpenter/040_karpenter/set_up_the_provisioner.md +++ b/content/karpenter/050_karpenter/set_up_the_provisioner.md @@ -84,10 +84,16 @@ Karpenter has been designed to be generic and support other Cloud and Infrastruc You can create a new terminal window within Cloud9 and leave the command below running so you can come back to that terminal every time you want to look for what Karpenter is doing. {{% /notice %}} -To read Karpenter logs from the console you can run the following command. +To read karpenter logs you first need to find the pod that act as elected leader and get the logs out from it. The following line setup an alias that you can use to automate that. The alias just looks for the headers of all the Karpenter controller logs, search for the pod that has the elected leader message and start streaming the line. ``` -kubectl logs -f deployment/karpenter -c controller -n karpenter +alias kl='for pod in $(kubectl get pods -n karpenter | grep karpenter | awk NF=1) ; do if [[ $(kubectl logs ${pod} -c controller -n karpenter --limit-bytes=4096) =~ .*acquired.* ]]; then kubectl logs ${pod} -c controller -n karpenter -f --tail=20; fi; done' +``` + +From now on to invoke the alias and get the logs we can just use + +``` +kl ``` {{% notice info %}} diff --git a/content/karpenter/040_karpenter/using_alternative_provisioners.md b/content/karpenter/050_karpenter/using_alternative_provisioners.md similarity index 97% rename from content/karpenter/040_karpenter/using_alternative_provisioners.md rename to content/karpenter/050_karpenter/using_alternative_provisioners.md index c291ecd3..14d388ba 100644 --- a/content/karpenter/040_karpenter/using_alternative_provisioners.md +++ b/content/karpenter/050_karpenter/using_alternative_provisioners.md @@ -125,7 +125,8 @@ But there is something that does not match with what we have seen so far with Ka Well, let's check first Karpenter log. ``` -kubectl logs -f deployment/karpenter -c controller -n karpenter +alias kl='for pod in $(kubectl get pods -n karpenter | grep karpenter | awk NF=1) ; do if [[ $(kubectl logs ${pod} -c controller -n karpenter --limit-bytes=4096) =~ .*acquired.* ]]; then kubectl logs ${pod} -c controller -n karpenter -f --tail=20; fi; done' +kl ``` The output of Karpenter should look similar to the one below diff --git a/content/karpenter/050_scaling/_index.md b/content/karpenter/060_scaling/_index.md similarity index 99% rename from content/karpenter/050_scaling/_index.md rename to content/karpenter/060_scaling/_index.md index 0f4fc965..019f0f4c 100644 --- a/content/karpenter/050_scaling/_index.md +++ b/content/karpenter/060_scaling/_index.md @@ -1,7 +1,7 @@ --- title: "Scaling App and Cluster" chapter: true -weight: 50 +weight: 60 --- # Implement AutoScaling with HPA and Karpenter diff --git a/content/karpenter/050_scaling/build_and_push_to_ecr.md b/content/karpenter/060_scaling/build_and_push_to_ecr.md similarity index 100% rename from content/karpenter/050_scaling/build_and_push_to_ecr.md rename to content/karpenter/060_scaling/build_and_push_to_ecr.md diff --git a/content/karpenter/050_scaling/deploy_hpa.md b/content/karpenter/060_scaling/deploy_hpa.md similarity index 100% rename from content/karpenter/050_scaling/deploy_hpa.md rename to content/karpenter/060_scaling/deploy_hpa.md diff --git a/content/karpenter/050_scaling/fis_experiment.md b/content/karpenter/060_scaling/fis_experiment.md similarity index 100% rename from content/karpenter/050_scaling/fis_experiment.md rename to content/karpenter/060_scaling/fis_experiment.md diff --git a/content/karpenter/050_scaling/monte_carlo_pi.md b/content/karpenter/060_scaling/monte_carlo_pi.md similarity index 95% rename from content/karpenter/050_scaling/monte_carlo_pi.md rename to content/karpenter/060_scaling/monte_carlo_pi.md index 35b9a3b1..14d10e5d 100644 --- a/content/karpenter/050_scaling/monte_carlo_pi.md +++ b/content/karpenter/060_scaling/monte_carlo_pi.md @@ -104,7 +104,8 @@ kubectl describe provisioner default We can confirm the statements above by checking Karpenter logs using the following command. By now you should be very familiar with the log lines expected. ``` -kubectl logs -f deployment/karpenter -c controller -n karpenter +alias kl='for pod in $(kubectl get pods -n karpenter | grep karpenter | awk NF=1) ; do if [[ $(kubectl logs ${pod} -c controller -n karpenter --limit-bytes=4096) =~ .*acquired.* ]]; then kubectl logs ${pod} -c controller -n karpenter -f --tail=20; fi; done' +kl ``` Or by runnint the following command to verify the details of the Spot instance created. diff --git a/content/karpenter/050_scaling/test_hpa.md b/content/karpenter/060_scaling/test_hpa.md similarity index 100% rename from content/karpenter/050_scaling/test_hpa.md rename to content/karpenter/060_scaling/test_hpa.md diff --git a/content/karpenter/020_eksctl/console_credentials.md b/content/karpenter/console_credentials.md similarity index 99% rename from content/karpenter/020_eksctl/console_credentials.md rename to content/karpenter/console_credentials.md index c6efbfc6..9b890e00 100644 --- a/content/karpenter/020_eksctl/console_credentials.md +++ b/content/karpenter/console_credentials.md @@ -1,7 +1,7 @@ --- title: "EKS Console Credentials" date: 2018-08-07T13:36:57-07:00 -weight: 40 +weight: 30 --- In this section we will set up the configuration you need to explore the Elastic Kubernetes Service (EKS) section in the AWS Console and the properties of the newly created EKS cluster. diff --git a/content/karpenter/020_eksctl/test.md b/content/karpenter/test.md similarity index 99% rename from content/karpenter/020_eksctl/test.md rename to content/karpenter/test.md index ff1f14dc..8f35d89c 100644 --- a/content/karpenter/020_eksctl/test.md +++ b/content/karpenter/test.md @@ -1,7 +1,7 @@ --- title: "Test the Cluster" date: 2018-08-07T13:36:57-07:00 -weight: 30 +weight: 20 --- ## Test the cluster: Confirm your Nodes, if we see 2 nodes then we know we have authenticated correctly: diff --git a/static/images/karpenter/prerequisites/cfn_stak_completion.png b/static/images/karpenter/prerequisites/cfn_stak_completion.png new file mode 100644 index 0000000000000000000000000000000000000000..a986f7842f5c77a0e2dc6d21e3ec509ea065e0d5 GIT binary patch literal 82156 zcmdSBby!?avH*%ha6-c18XyqD;5tZfLa-153GO<;po0f@NPq-~0KpxC1cpHdcMA}F zaA$Cb$8UG<-TmI%-TU{O?>p0dPFHoEu4<{Ssy-1KYKlbo)c9y$T+|O28X8u7 zC>riV`uO3}_>PW-`H-UjRsJ30e;~2LzhnMS9&PWT9PO2stg`Y$s%7SEVd3Cn?dW>m zqEh;RIA!xr*Hu^bjhLAunA^nM(bR$)3U>Mn1q}ohd&q(DT@hnt645}%Qg5#(%cDfU)Q;UDM^cM{CjuC7jE zK%j?*2e$`5x1+NakXKYx6v)E|7B{r&wtEuc34q~zf8k7hkI5cpRKke8bW_`h{Opo0E-DyCrr zwXpvnX9Ip<&jSrfK7L`)-{Aj0CI6)OFPOUj#N-v=75Z1ye<}KZqiVZYILkVMA85Kt z{!=vnK>pXle;|T@e>MGISn+p0|Ly4mqb2b{!2cDRB)*`#+%q&ZX*6ZISMQ+cyBXN& z?|uWi<6J#Ff<6f-)w_lZJpV3$Lxh3z{Q0&K`m48cjhY-=`wzd5UICX`iz&^Bv_C}# zwt(SF9&ty9X782*xbVP1hQ~flJqCvTDuNydXRIorpwKvTPV%*F-D3@m*V2!kqqBa; zO8oS1ZeVXP(mZy55Za7uB=&rfh0WqIm1jTwx1j%0$xq+$4J@8AA+(FXhf1$S>&$nt;mf#Aqb_#N5p`CtDJ zu_Ln{;E?x}+j##6U^er23`k+64eNjEb$^$<^ac9^+=W!A7{>nrScq3BP0MNjQTe|! zk@W6Sq4W)YEb@qDs^}no1?W1gSh0}cn+}IaOv|g|X08{@&mdP_n2H~jgfkUsnC1&Ajsny(dm1J) z@lvF3n3J!!ODb+gVmtMkboQ~vCi&~rBcm63Fb?Yo)NFt&h@zeIHC?)uUxoSqegyy3 zBZMB>O)C_q67H>SyGfB+wevQ7$0lSds`Y0EplG17tYYfBtH6IAslaIIAArdte;AVH zoL)2TQqg)eyW|8lN?v^W{_$0|tz{&=Xak$nmu7LrQYqOi3UyWO8upxnq|EOhnt-FZ z>_VJhBowL!RXhY{c%oa_Sk_vdOe5uu$&!Ch3;}dS&uLVeI^(}_4 z?sM$CSioQf3o7~iu)kRAaUdE!B9FhTfa$;@SHNNWK+-==^7@+bsTxfZydpkA!9nE3 zDX}43C9h#Y=ioeG5t2Iw6s+#KE7)yNlAX%TUu+nxkMCxtDR_VDEB znhKuOh{ce}NB<+X&M5kb?G5GEMv3a`n#>B{UlurO7W@hOQI-XbM#W}~gX+f{92WfL z7}Wl-+mKUld?%V4LY-RKuzK5Us8XfPJW-ZESbwuyh*=&qm7eblIOx&6)*ezydEEKg z$Ny@q*y**6|I{TK=TNuhrnlqUMy!Po!?XVKu^ijxDvuNHbR_t{4v;R6vezIFbX%QH zx@t}~){k9}t|T~9xifjF8}|uS|3F@?%k(bRX>{N@js$-5R6RCaB-*KXVgB6&1khn< zdi5uv>xZEaSD%#{qnjh|jv?rNouPr*rIC;UkyUJw|D!5b>F#$^z(m5v&7wEA7N44I zOX<2@d6_sQL2Ou5&Ko#KAsI5(kxggiI9_V`Yf~~y_Njt>?!LwNXgp%d`YFR=imHoN zAmta(*_K#Vkl_9FD}(+Yx!XR5Z3Bl{x0>{kbb`#jpdB>U*ry@oM{`%+!>o~Q8z1yC z`RQKge>u8R{7)maLOfu}c5O#;IY|JHL+(D_0p8dOy6G)~z*m`H%jmiY;`JP;&#>fd zdvh`0H(veh0N+*`hvth92HzvO+u z%Xny0-ZL)frJYm-NO^vpo?`U8#Dj07ORIjVo5!U@IHy;=zr0k0~{NB4Y$7CMTv;y(T}j0^3{< z4K4~6refJ=U{T)|J6`-=UB-r7qHsjA9wb*j{)#t8?z=9RQ#%jIQ9&fjG0XJO+%w8a zk*>JpmF~{B%7yrN@GffAIayigqIJFLWUxTy3Uv}4T)Qin?`6SWQX0-vkqYI2RcDuu ziO#0_U!i6?_C1zlVd6=zRX!wie6Ik|vyi`DjTmHeLFd9fx(O_#VI8AcEmIEw;qv+q z=`K5EZCpnEf@Te~PY=1&7Y*l_feswsM%8Qe{hIvZ_&WKLGvp-hKP`}kW*FPAZZL?s zVOjus7^h*)USxi}guS?T)Sbz2-wYQ9P0*BO=a*S|dzx9EFsKiGgKHRLU3;2{?T>;h ziQqRKl$keodJ~_T?aOc+7fp60J_>uh<+~0_%99H1JkoHK%Mi|kG7=<{F5yfLY!~{i z4ar_PZ5v*~ez-{<8cMoe#`=U7SWI@fljuKdwIk0g)l%I9F%;N*q(g`XZr?$0=>AUlh^$q+5g)o@4BU}wtU$+-Oe+|ID_ zQPX{`wh0Jc)OK4!BUw}n>Gb@c&V@W$!#UKhL1$()kN-7ai@f0>twP2?L{8K>=UD`A zV@;c#n)1Tjy65x9-Y4rO73<5gbt5Kc=|X`XO5I09-ZoZBlo`ZsKu(vg-Fls0C(N-3 za$3W_Y+l+$=U4Oy)O2XQU@}W457ccf4O6OmiiePXChNxS^S7F%)D%ZG>B9ST2_L3l zL}V2%jZAr11c6JNOz+SMO^6w>+PQH+iAg?qSC|JX zcxn8ptMQ-ll|rqyr(bSxT&wRC5qQ59GRr16;b07Hdu{3DXK(cQ_JThhfg}A{D3lZz z&k7xKJU57oRiQj>LJUA>n%VZ$OK5gx`s841)I-*o!xa^-)PIH$d-Mq1M9ee2kXVyQ z#wp#JEbrE&wk9Hp&#v)jFt$NB;!u96rsh=-zF8h$7kTy}7D0U1QC>95a^+>TN3hr@ z)$65~ig>}%T&aOpQ8w2ia_w&0Jn2ULh|wj=}K1+FRdcp?D3gFwj-zIk2Bi0Yic9BvQ$ti zy^WHhf`k9G(BoBLy<_x4S0v7i<8i0V>$yPSXbSiSZdQYDgGMmof;t3$u)OC4nO;{} z0o$pTzT9au07xN3m#B2Y7$$)Z?pP*!F7VCfO>%Q{mJ%8#B=+(rcDXo!^7JMWOwzG zkuOYny^-)#VXUzh8>=&+6~oDnlYTicsnVEAa0UPpw9b6xn-F5BOWR+S z82nudzD{lGjws!b(a?@_gkP;z<-`%Do2_>dFWVWJL1rm~9R&ZA8VXax3M~0lVeRU1 zuvoytu09&lpTw~gJFh3?K9HQq1u^YQ6e8*jU-xCI)R_R+Hwk7Y>2?rArFvFlzdTl+ z)9x6&O#+Yyoih_g1tLRu$PS#|O#5Gs`qhxmbS2amzR6!ANYBn3WENPd`MpFGk@UL~ zkDkj7Rv24}KIu~_(|SK$mjhClPk;54k&TVp=u}LyNjtofk;4EkhFe~`~V5t z!P8-@;1^15nVlaRxJ=0r#V{LyXpAv@rBs3z`>0Zvj*t-PyVFY=XgZsMX4}UbLhd}b zQv3?sWlW;G>h>Zb);4aw7vqe#1#5Za#Nyb-+i2Wr*PLrVjB>0z)V==rVo5bxex=k> zZtHV&-sSAs2o>oP8UIX1o7PkUlK?iT&0vtmE>YgcdYbJ_Mwp55jV|7*$)N;gY*ug5 z1OF|V#MNB}q1QQntnd^JS$$K-^%C`USLsp*eJ!#OvYgsf57E&{VhtvVK#+G zz-&V#mLHxj8%3MIT@aBK(2VHU$e2@oZdBZkG@+bP{P*%|RmV^dn!Eadlz$@bw@!H5 zc(REqO#xOlrU3M1}VbAo8qJ)$j)qMU4Eghs#W7H@6G1a+>bD{tl{G2A1kzZ)wmZ z5m)8jhT`1{mS=s=OLB#_Mn`)VHiukTugKlL{2^NC)H4cg8oHlOX&hNx?(aTMaArYM zP09F!MWjW!gu*&^POzSf}sZHhZLzYNnq@?xlzDm^a8u6&N82G$dH}Ev%A&crmHY~HX zD#0~*(g5_k%{|=Qro!;`k9qbH=8(?5+`91s+Np;29Y4Azdiu{$XBJPr=FX^XmlWvpnObf0sYJ{7xgn@b4{6%C9vkm#8Dp?K6TH1;`+ z&-UL>a;-bEOJBW7WiRF%Js6($CZxS(b2IdL`97L5-u-M@{$q<-frfe(r*?7yiTctO ztxm(?4~YruW%?~(FekLulvk*Pg`*u9U3$${YifdT@@VN)#JyisQ7Ix0KaX_y-9#IP zTUq{r%g;aQ$4fSZheQi)MiBcCT=IFn{5}!_=mK*2$dlD%0p%J}Xc*O;%x>Sr=-ZJlK3OAT zLM&*eZg+BR9cMbZFIwk&*w#SyqNy!-e&Vwc&+fS+ye$Aa#>2IwV>QQZD`D2++E>P4eP+fXEK>HRBU3dMQ6H$O?<1*A?CB%s>z>$#dIx z!@{;%f{Skc>&GRwQ@e|c9!9r>goHmH>|p@>zC}qW=f?2i={w=lg6rk|l6w(uG8Tm& zcw}?e-4}i5zwdUw`DbPAM^nURF8M6n+}~Nnif{_rY8-e9@6|Q~Y8UUM?%teF9~KW! z8HwyI&U&7T8L6i;UK}DvvJnD12Y*}-+vSV3HY7OTPaKoGv8$#3Xlbx-_NwKuh#V2k z;L?M~cg3bjOWjXoJ!Y*IH~Qs%9LKw2y;l4?KKfwy3nHw6;?Z%IKOJB6(R!~uYb5S{ zjF@Mi>m7HDoSW^GSN4i-le+|X1{NabesL1A$Br6hp>8*O*>}9YCxg8;3%}j!(Y$Vy z-WtmzayNum##$p@w*mt6(9wmADqc*L{}BQ&wviELR?PqM7#1>XlbKB+0Qd++^F5Ce zLv})u+_Qhu&cWT~3q+bp42LfJVZgB=)rag1VEk?yua-f&~1dS4wB5 zk0&X5u8AWu;E=`8#$nTG_Ac*$?o$xqt*D!X>v4yc5d}k|ZSGJM*e-67v-2`(bV{SnGe${-s9=dL8kbzpy3)&%eTY0 z?AT_9TryKwNGvoc++cs!W&vP^VSDTTdzaT7Sy{5gk0>#h0=A|10+p8*6t5lo2$L+p0GE6A? zmvfN_r~=&njf_pNOB-q|)@`-ZwYAedA4{cww;=JyjQ*(LfYQ!I!Xeb^w#uTcX>^b0 zwXyiURJp_LIaoQqpw^-%eSWgmTb<@U-0kG7H-Wt_ASiUYy5*~S-xHwrLY?g?wc_1; zlLr_Rhq!L7S4a2g#k+EoG!f6@#mm@0ZW{Pm)ba}Zo9d?=??0YJ-KuBt_UCT#hX z|BEd^fq>8E#F`5gOC61n!**TVrd5o`PEPoAyL^YcX3(m^~JLltE3f!mvzwzufL-z6FQ#6}% z8^cY?1J@j76{qnjxS5VAyG#ElmVt&?(}BkO`_KiLo61brA#q&jWz0^xIRA=avsanc zS*qYpaCb_LRmPa?QQNGa>{x8=?iro<8nk+et=D6Jt_lfiy$nCb=tk8LXV7WKGDsmW z;L?&J(X@gyU*bH^)}|IkB-3KuHg`hc>XalnW z2_q;oX!0|daq9l_Gr}zN_GE8raqn~{(OdJN$^D?p0PeGvlB#%r?R%d)E9A6SxQN26 z_Z;RYTXfs(J{Hgsy4dfM^rPc@=g#jwCnxIA&;^g(7&taB0*9od{nWE=fV&!cTxQNI z`f&6QGcoXnAq980MR&_>w20lDEe4m%m6BD0DoQFe9_@?8=+>gFPt7gS1*UwU@8UZV z6qjcLQ}Jz52!aV{AkOe)>tRYLyAWG{B)S?Q>>`{$OFN0q#BuZ^yp!NgKIg&g z7~$b{Uz`FHWc7q!VUd{ZEPG{ApZs=IKW+CGGbLv6!P>1C(|WFRbVDB&3`#5#D*ili z_$1+?=j-h|PjmE0GEp||-k}9@$hgz<)LGb4mc+nNjQ5lX;efX7hXxkb$ghb*iy&1q zA)<9swp;5xE5(bO*J@VNDx$9lW33Fo$7uP#?Nr%0@tn9qADb*B!Yl+=ImGLLWIZEk zB;fWyNs+LXl!H~>)eKSnI0-D3o6SdCbAwuMEA;-r)%MoZwL7+Inn>VX9!yJIziAB; zC2k!xR&L6s$;AN%%`Pc-GZGP;F+Gap)WGlYREuBoRi#K9bM?O!KRJvkh;qMkywvfb zPcJC^lG(L{3qX0cNZWa#HYKZ!)g;zy*ED333VnNl=K284pfz*JB5E`0$58w{2b zi#N$3sZZ=*D2H8M3@!>MEcCw3sAlhfX+w#~*RuO@cYUd+71>Zk_dB7}-S1=Tmuwo7 zNHkctbMw~(YT;t_jbWqjS$_1=1!@`j90^^@QpnC@gZr$_D8qRUmGyz2#%;{SS{1x6 zKk|e*Z+Jz0tI(C_;v2Ebs>{WCv^*Z2;;+@MHviluKl)vzv|rX`rt!rL5EL%vvG)r6 zvHrWGDAu2CLA!_$jmW1jD71^MrQ+sl_sZtEXxmG*``6Wemm9UH8J`48l+LHrpS4{03OE4Y)>5Wd#Jvi!9G*4`@L>v8j$2LkMERN^1hbu zDaTEn58Qo^`jAAo@6J-_EZ_P5K8_@?#4oS;#pwXP8bB3+p z&dA=U*(VZS0;W|-^b&rJXJ|)@MY>;)PzjN_ibO0vU7(FLECI_CyvyB}izh5F7^0t7 z{%F-FQ`l+SIr2&GR@*IZJ3C{mMx&ovi4K=L z2!c*xkP;N2zG19OxmbU@YgkyjSi&)HGk?ba@h9|=+rdmk_0fSXyhx@%E$bUh+++Xk zFMRFiChfr|1}&0_!Bd!Gp4(64BaTjlao35*4E8`q!@86%s>J76B(x%OI_3I#_qP`k z+U4(%dC}~CFsP3NSTP8PB#(kYFjv4P^RA_TCP25!^le4ve3>#3d_8}@JqnPJDj-So zMU9B^*MrY;sfM;B}Dw~O0k00^%)|F0(^ww z86SED2WbhX>vFhVac^@T=&6Z%AAQfRxB~+{xn7H(g~YItmrO7HrlCF-gb!Ob1Fz?N z9ZIy0)u(Y=Hor&L*)MiiNqE`&AGM!gn&AS>P-pVgeSsmo_QGlU^FFH-SxPA~m}5$* zHp7~&)sTxLNt^L)Fu2#x(H9j|AVipHj_M0#ujj1{(-Ef3<(A^MuckfVdMyIu zL0;P1pD>tp?%-u29@g#nD*_5`p+C!q5kiVF^eak;&_j%{?#Jt&*kujIJ?yBU!ltz4 zp=PVw;75+He+I#?_$XLAYo<=urI0n`t;aGeaY>F+)WvGL#xR)ZASujx8bA zUM&73H(Wr*;5Zt4;8bW-q7`7`5=WjtUk`9I%;xT^9N8+5Ft$M%3m6uT=MTHCBJ`;C zT2~l+1Cgm?xf?pmWszbcQo3^P69HCiu$|BwA^mls?bDl_`WRUQr4+Xv;l5h5twNIF zb%bK9*!FP@P&Dat+%G&5KWH_Lqahrf$%Xn_zQyb8YQ`r0ejxM3nr4e?)3AAF(~wOK ze>4sFI|C|Junv0oma5)U3^TKUlBL}2dDO9Q_mDw;1MGSR*cHxBJ_3YZ#a_C=awTI~ zyTU=~!`J5wPOB+I(KOvTiymj}Wlpps0O(v(&BN|R*)yFclknO_c=*CHbHYuB#0A4w zV9tri=t0%WeSRfqvTXi~`1_mwNrtGYrUKV@SdP#XQ;^8}WR@;a6(lLv1#JW4?7;ST z9r)Q3H*|?)0s66H*2ndfG4%X7YzVR^M}u1EIwe{M8!kglTQ2+{R_ix4>z>QlK<5*> z17o|`mWoSk?xiTekkJVZIQd?7NjH05IiWyV7#4%OfjI*H23 zRPs+n=k>qu@hjUPk7c=9-sfQPSzMkm9qK$!6R6J+H9J4?IA!b_2U+88uno+ozY>dm z5f=GbXjE7q@$ky0j0(P#aB*G*K~@y#5hx0`>Y7m>s7y&>tRB?xiXP`~_woOXZ(R-4 zNfIj_L2i+RKL)Cj{=GO2-L7FGhCdg3sd(a>l_$8@mj_?1XK)=y8$v}5<;IcugKq5K zRE)@6rW1ZM=zPN|y$Ln)P47!>ob(EaW9Y;1u;A0qN!Hw@H=p!TpS6nve&s5`JB)ya^gZ ztC#7ivRq)O0V3^c^)xpY)=?F&83bb(^-yV4q344ejs(PaD|bS6cEtLA%{DMV)wf#_ zQ$|7Ob#8NUnOIkP+IM;f(*%cV%aeBiX7qVJUpQ7MUn%AZGW?{(9W7_wm_91d3To4^pYdkLLEc_z@L%5NVd5~u1UA*>^2Q%@ zOJ?YCA4>owNY|51-lMMvRY;z6$oalRpjD%1ThzR5)A?>5=Wn}+4OFxZdB*y0@wNWf zqp1hK+2M!AW$O?c`kpM`v2R}f&RaPX8H4d^1xJ)j-OIqp9F+zD+1-vajn666D6-X%H zoT;pu6`ME$DpQ9u|tRSF&A{Au9;K>7Nnl2TwV**(p?RXB^SIyL3wR zGnyOXqC6za!?^~lz-JoPZ)_FB4ydml{2 zOg0g}<=`J_QXi_#zq#OKo=M&FDZzGCGf(NJRH3VSMkEZ9l@ySf2MtMV-U{dr>Ktpf zvucFg;`m;Ey}keXyr*3lf=6RIsuoLdv-@7PS9@?-LLCzX)jv7uA7G_m$H~cNRo?Dxt2`ac;rxh$UO8QuLp|t#6 z3g7Ev=PXRX5aF{jHj_RDR)?W};tqvHezil1)@H`Dt0*k21Kit}tD*rukfCUL{`W&v z156-gER`h5vP9*94a|XA=-q`&dOGm%qN&v4*wRPRK1poh4iy7x_jt5Ul#EObu#?H0 zbL@p$4rgu@#@>4vNB>#;Sz)!>vlL^(qA)7#+fs0Ib00QDH#=UpH*2$5`DK#1y4iG% z)An2$_w_ffB7GImAk`BEQGF{Xzm{tUqF<9xGje;Y)bCjBi~%~-rp$y#BitP5tfy~T)w$QSy+c5nf^>v+u>U;f06>$LVqN_BquGn`vdkN!K^ zXP)U3ljilaQWX~;u@?5Mgh%&}^*G#MCyAnmS^;JozXspAYTzgu1Pfi1Z);peqwsf; zuLl<$)X7-nay(pwexx*YjjR7wgji=hj}==GqL=Voosc|>BLyrBWF5}K{4@C22Da;{ z!r_(VQ?YMxR8ofWu16C0{FRbpwz=?uk^*5kEy7=-ogBcs7rzk#HnYzS2Tmwd7tWf@ z1z%eGm(G418b5O~v?m3cM1I(d%SaV)JYY~OMK{yVd zwQRq`e44Z6Oi|C|TvHzsPM0rnyszGr&$VDRoWdSQ(iED(V{v0c5;Z_bjgqomtRncz zWd;bTT2lSPm-mB;t2zPbNZ*${>ZcmSh#hLy9qs%WI4F52X};+c*4n@D{$!kFF#;6= zV}Kv1WQh5&5ikZk;&P|mNOM+Bc!qRh2C$MatCdVk3DR`K~t>}llPx373jyAr|v8+kT}E586hV_hVohaU-S9n^u>mZ7R+M$n;D zvf<>xk9oQl!mCBkhBeiec^=zU^|UQaGM`PBYOA-W5?);5J{mRIZt`}VFUDq~#iJPH zG%6>oVrNsO6iyWa&n!PD&IZpU9>xxf2;)EHP=29Po{e+5qvVCGCm3C<$?x;@-5Rs- zdNKT%EKGyK;*IE&uA3Bo56RQ}eLV_GdJ!46q&WBEwb%PgfPQg0^u>{3r!`gU?G zi(nTjDM)0ZUX>41Cs89Yb9BckjWFx{c$SyjVlJ;)ca*ibS#d^5EtkwCAXIm_FHsrfEP~XaZ+fY;bswZ*seeRr4BOxIH(0hsr7tsxwGGTt9tFoAc@oB^_9ymR2pY zW{0!OpvP{H1t!c&E$vxR)=^BMz*fyzYVD5smsaZEC6A@Zn6s;#B#kAPad3y8>I6y0 z9o=6aiubnXKyI*}_+Ag{#OCbAyg8*2%I3>c8T)MH|HadKx@m}NZnyo8G`9PctwLM~ zPbxPbs~s~yhm*8>{r+N}5bwLL&*5?RgH3bd?~!{;on)UF?SoBOh-uoUrwnuDqB-UF ztCLEBD;hYvtn^<(hSCyQJ{YE6B3~D9Qi*Uoc$^UnePeoMS)+V>KFo{<6Lb;!?j7J! z^uGMuW;>SmmpPQTVP4e{vn>pfju)SA5v73r}=R zQ*BxBPCQWGdTj2TC|H$2_o$I9|B^y!@k7=#<`9VL4lO1*6 zv>95rd0Tasph}&@nrvcw9MDW2AF!<3edf6eZpnL10YJx<+C2P#v#DtHcHL+|z% znss12UnnT8rRb--Xa(EZVFD&M)(x-~l8%-TuI-#GopQs3v(Ipgej%7<0ET+j9Pb|E zEp%is@}XBc)3_g^j@J6QLX8xJ_Wm@fcQ~;Svk+~R0%cR~ZsFOlwe2E3_&_W%1{)$% z4PUf>TAL)ueW)@20>-o9F}e=H!%hRar9RLq55Og%GbI@yv1rnbsP=(LS&*!e%z2Hc zU1l-9!ce54WOuL@^lagcS+(n5ydNMN1@9GGEQ>yB^IX{dz}#p??1V>uI3pkxsYuAGWYG8z+JCDywW*4jvp>vlf)6_LcYv1Fnc2-3A;nC^+)j@c{ngf(y~@V16YPLJZVT^vq({+8cRVBZHfF8 zc#ibDvx!caUbT73oN*fw^&C&zU}ot}20GdOtfV@&(fIR8%GO`u1)L&@-=3`$(GR>d zd^7$ecgBqdr?Z4%2y(=t(pVts0@@S{)ZMdvz2wG>dX2qo?$ajjdlzUFHGwYHmlOMs zfMpN^hRw3o=g*|q!2NuEj-#lJ%0ucd`X$j_PU2iHLN|k#AJP-!T@&Ts7ztIZS)Sz# z%B+R0I#&lmwmh3Jdt!b*0|KSc=u}Kg3P{Q{JMbWYB3??#+ zDpb1b3~7B-qgas@bFkB6^$ZZlyRt>bp;2I5f43nxyxE(~OE5!=2)c8$2*yf4KYS)1 z`D8s8y6b)o1AylnGn#MmBR((ntP+KnWga1mBCt!M^VGh^OMHs#QFOfSjI-wA#;IB( zzHddC>Kz=Lb<%EVY=_nvjFW^@ky3{eQ(4CcHeVyGb}t?!vbouY6|i;u#ED6ZOya>Z zEg`x(6+Rr`)`U?)^flHkhchHRGI?$`5p40FnS8~FF+Z#Mkv=%?!c*(*U$kd>?oJy< z(dLZ&F43V|sK)CJU|4~^p^(ZyL{Mewwh&%u7t<|`Flj>D#CKa>c;kF*p3y*Cjy#kU zx^5GvDCBRi5S*W{+hV7Ocx;Sf2h?fGUK2^+Jtb`zwEAfk%5XS)2@ibl zcwEB8)y^n=)mLBl)UXaRppwM7Rn`_a0$)pWVbgeAQ zT^LYaiDbbiF}(=!U>nLS^WZGL-LKIu6cnw@ns3)BsAYT52lRsdqcc#{%HaVM@Q16Z?4rRC>or`rnuSP$1eB{yeG^JYS@n4GXSL7Vs>_i|o>)>PEYK zA7oX-9u$7A)@i$}Ou*z1j5^{;PxAC{9HTvDCTS-W%2q6h&b6U!^rmT)o(xlv@5P%r6AiFk3r(I%cDJlpQi&}#qrHT zzk5so01a!-Fl${ub9AO}l_g;gl?pN`OT!mO1>CO*h;LN+MONi+K@4JN9Z_dwz;;EB zm3$_Tc87S~-mUX1f3oE(uj9a2Ql^{^eRqLUn%Au)7%(TmZ}5n2N7okwZhxGF)=Q3i z2ErWdj<+?{LVhCLmL{W|)35Y{0SC#WEOTD7Uj5xH`;*;sRgU4Jce?-!vV9C76z!!! zU7(lW!qeWd^_rjJuE`P|m>@Ta6e%Ae$Av!nsx!)HYurq=OVQBnMmL%c@gZs{5t$CW zBg7M#)ZdKn9Mu}XMoZfd@*8%Q=g{;mje;qc`78%*&o$<#R?CQZV*oXYx5zKP(1W&t zI2#|KxK$N}5rz(@;Cj*JDe700GQg{F%JRGE*${js-f$+V`;%rV?exA2#C%a;8p$u0 zwZ!hi^8=|((xRzyBOq%{ONuH;=`gvrz(-gWPNDqZ%J@9}Gdw7R`yuB-jH!x{;l-lV zm-3UiG4Dm^A@!|8K_0J0a*NeO%8`VdAg6 zqRGx<`yXs|3!C^Zdq*#7t z@F9ME6(RdM`^c~hy2YRdWD>t3aS=fkV1wT@%FN;03q7&v7`rEL(tG^4bNr2bM`gDg zuY>FefL=uMJ_?q{sCf5qLdTHl z@`AwgHa6p3FhJq(*<)Esg_Ilpe=5wEq^&tN`t}%9QaN>4_k$)edWhmlvyUagy01@ zm0Bl39tU^!v(MA;4uH8q`b!RiQqu5(gNuY2ikiyMAbh~n1*rHNt+`@vX8I-Db`ZiN&$0q{WBmQkLp;PIty2dDF@5*IyC zP}S+9XoT6;%SFxUYIG7oqwCI5w18ZNchP(XA_zR}ov7 z#`EI-48@b)n)4LaJ>mh%k5+U!Bp#MCJKdT5CQ`W0zi^9mczNK^q!44fd^`OGP;s?C zyWn~_#=5M%A+PkQ^+829NHdWboRh0M<9E!|pNy-%C6bCHLe?u*R6KXvPIRV6yf~7S zD!yln+E|JcB?`bt65jCQ8D|azN4K`?^v1L26=`L(EK2<5-_k85TUZR2@LAvekSgUL z(3|sOJwvVSB0H-0ZFf%MAovM9sko97uqhx}^qjz*3tReD@3UB7vxI~aX z^AGOPu#jH!RMZtQiNC($Urq{5;v80oN+hUL=Dj0;^LaM4#&-pxrlj18+xJUw6Qm>; z8lEOY<~;Np7D!A4>N(7zSWS)SS~OtRG$H|*5?kMhVoSs=@dnYV z8b6Gmd-4igdt~Oi&^WOE%$=deN86^*S?g`zL#2oaTg zdRGzXxfQE=b+VPG-sHab#d&@Gla}IX&|^GIEW;w{=)(e%#*$wF*v5DrMWP};bKez7 zNL?sRSQ$xWX@x8hX!TQ#1X}L;KblAw#uY;{CEs7=;QE|wOQgbXF$7+3482m^eI@X0CsgOqL#qj663GB}98OegXlkcMgEgo+jJ3@yD9FWchVzVP6uuB?vB z#RHi)C02Fc$(RNUGm9Kuy`xnk5b<*YP-p{c|1fQZ*Uq|kX_1&bi?tC+V{@S6h?@P- zX|lSeVi0Vq7}Gx$KkE0Wqi|(LvSVKID-KhC?j%2u(&$Q8_Kar@Gh%I{1lilz0 zBr@X3*vvb-uQSF@WT%&7^^ax4X*<4aX@!y}u2l5%Uc(S^#jH{5qRUAiXR95qZ2=bk_rB5Z zF27efzG~I+HuC=y)jZ8SdhbW2ly!dd)=y-m+i7Zu&YWRmC?mTpCnX}c8B z_S9FW$52<_t?H+-Gq+b~XV(y`chqOIjMP0$f=(;wCfvrGzz%^Ds$nZT>N;OY{WsxE z@ZJ&G+E-Cnj;h`2v7{hYj`jfeAB?OMhhewSpM`pOX4=5 zz&pu7!rVl5;J{}$JFfe{gmOIGq@|DL=0U~yLc>aWeMc)E5Q4ySUSM2so>iaGN^{Rr zrW=rjD#$Q)1?r0w@VF0Xg=hcLMQijv&rCesAfdQxzZi!M-pBSQTXca(r2IOSK6z>bh44dQO@L$1Q94U^PYjeH;0-g2XgIv5SG zf5GT9o<>ky_2C3qu8rG)8Q!PHgeh!jNRKX&%O#V8gp6<1Z)evoU}v2d`y| zX6{t62j2yj2GnI`{n@&p!-a+MmvLI4zEq>f-W#c#lNx!km1F2JDJ^O0qsbdYzM6U!8f%o(@eLk8|$oDej^5N2I^ zv(^|D(BarGJCca|bn$b&hbhK^Gj2s5FzY)O@ZifT+XYPZ_xZT6IR8*`_~X;HK}vD_ z<})LlOJJv}CN&$&kSRde0UHbC%iSug_}5u-UZ^7~1S$W=0KVfHd=5b#S>QLA-4F9n z{E!_m3C__NMgrs|p-tL;i$ihNxMTTeXZJ8HAX#H#9Y4uPHOpCpV?K8H$o#`0Le{W~ zQE`^K(HV98hd6tPY90K=gO?;8&xfZ9zYNrg#4JgiRruW{Mk+32AYhsJua4L#^ zYU5+J=y5wbjp!!XWcJ1|BNOs4d1^R@x>+Ei?_6;4OY1%ojbDRAzSy<%i37)NjpQ{a zGCSC|P_!L4mhEKr$2a`1jI7S|Utqa`_>F}wgiLcX`)bZAx zz%A)4P5%B z0FYeOo34?cL~+vL1e2ZBH{fo7htZyK3#H=jbk%c7Z?&e>3ZJQ?ifgOjh)-dhp!Zqz zCb7(Q4eU&#j1naeAB_0>w*Y4ENWZN!ixVR?Auh75TAol$1k343glsH3mQ(uR^qXjV zJKtzg#-%#0@7AjRli&`G?wH1(yS4lKv`4i@(bof11B=sbSD}?pb?rx~ySi#y*wPUl z5PuC%icaJ8D-1^+4`u_#`#Y(=sx~I26^`Hy3)Dx&3nCBNN)mk+hlSPT5#F6>LD<7^h>pT*zcryz<7i7Tn43G~G;Q?_3(ol1VmP>WWm;6dg zY7kAWFxDiVdaFS3bcYYNPVGOZ4Q2g7VTrZ; z*VXl&cIZQGpao1V*c?>*=HD{I!;YbJZYe&65o zYy+Iueb%!U*C0vLaJg4%(_PWb({~#Xv!&RKX0(W=7s_Jk@#OHDI z(J9?4cs{F9ko|%`2XDw=O7+6%_wdqbRJPtbn0Vg(r5a2Q>;NaY22YsYVfA@7*S62% zYm=867-9m8+sz+1QXAesdv)AG4k+v;YkxPGelIq~NGyMynvmS=kkkW{MAgyD&1fS> zvv-Dm#4;~ms)_Io5cw@#11iVo!~o6i2h{`CU) zO}MMCxJ-sYI>Ku@C799Xc8SMY+cL+yq&yI&z1QV--a6v=wb0HNCh#(Bh#aJPxDI*^ zn7E%Cuo*0IzZaR!H=_pWFToW9++n_jpo?yw=0^}l1eS9Pqt}psRd}4eduDYWann&d zL}eRKO25tzm-+M~BPSd#cgBVG5NtV6s3K0|Y&Q`()}p3cbH5C3*Kw6zzL`(*U4<6e z^DEksZ^w##<({ zV>8e@h45v$h!hh=+Lm-v{dFwF^XapKn%ptI^ZT591gHA5c)w04y<5c{92a4FsbK^7&&Y3(;map816%z4{C4 z4KNAtiCpum#oKUt@#CLb+bDfLBK&qqNb%w0qZ6iY8@@qsM~DFz|NLQD>pu9b{omy# z6`2Ap^<}UOJoJRI;sd%~6S}fUrNkCG;{;9&6G9(|l|A9wIr3jF_|0M2?RGZ_vPj(3 ze(jtr2F`g~cIfy(oX<5a_^dJ*2Ov9p#LG}T3rXt+*9~}}=VHZ>hN{x?=q_ZnQbAN5 zO@hH**KDHa1cLSQlE}4aTG!p~eb4wH$tqK(Q=Lt;)TWbID-Y0&$CSeGfI-a_v~@B& z)!vgaX~e2+V0%$=j2N)8BC@$f3MCYiY(7)}V$IU!6EEA5gb#%j#9uJXb3BHYyzSwD zUSpLj%axeaZmX;06dicM&j-WfO1s{U#s0OL(A8-MwKlw@A3vm}hB{%sc1)#PuT{qSJ z9kFHBHGC#)vUzoNT>$iBn=wsLKUaGh?1O`^J%JnSuI7>wbhx@?_Dnzs; z5f3Mh-#RiO23Y*;lEMnKMQw^5un+E$sJUO!Zh_6vXLzWEksF{;zox(r`YlihYpB{m z)fdJpngfPGW+X8pEnlkqk*T|b7xL5H1{O^(+N3c#X$S6zxcLH7c%q&gU+r?Zj+?9-Ef?y-JF?Jh z)!@|dFw9Zha{&W-fiSGyrj5(>EFb@}UvORu0m2feM#Q-L`j3%SeD*Vm=D1ba`Z4}r zZBfFc3jVOiaa4S;9zJRLXCytij9l0Lia%r{8CyN|VZSSc7#?<)c2jqYZW`AO2%Zm} z$nhcE{Jca_qhv4?YM~63M^u|V`xsWBKnjIquv@Nu!k20M3|bRMz5W;`)U+(1qlV;s zd6R=0Yk0I}sho-VCXDRjk{B#3FYwrJ;IaXQUU@C32!fn4e_r|!!jCs~Onz1_D5LbB z1LA+HHUHis-#`9!(8T#`QE8H(2+%|=%VvF*l7V5^h>8A8UhChYF8~SDp@fRq?_2a% z^Y#lZh!W?g!l%VDoNCU)Llpj3#-5i2u#x5kBC;`r z#R|C0QJU%!XNqT_JtNcZCeEWv+Yb1N*{tcZ4UN862_MC2(47e{*CUx+J^yf_{<97F zG8){8_Qvr17Q>+KGGIFG@uSL%(EX8_V@~_EM+w_55@m*+%%mT$%Rh{v)ve&HQ9}2{ zV|L04%#wkC7LZrnA#{2^Rm?HX4Ciwn8Tl;_0MESlW|6?rx>C?Ue z)!EZ2z~y}X6@4^r<(oKvJ2gZwK}_oe^>8LDIh{?~hADgb6^Tuf+f>6XTu#V9xg}IU za%10BFd7V9{z2+{E1$3{pVtoGpKx56ZmGt4cw%gVo_=|Z^|ni9#G==(%$c!*vt0Di z0cwa11W)OL@(l`FdrTI|-e^F{-amKm#0w;oRJskw4o3qJL_=O2>6m@E3kiDPV%_`p ztodD~+c5ESd8iB&Xb}e(Eg!yxd|U0XAn$MwgxdbQflE#Fb{U2dRHX}Bz22>=)~6pI z6ns)4V~k`BJIoo|m*a(bmf4MsDpnw5zRkskLR$U6$ALbN^na75|L_BCvXO(0MjV`t zD2u)r>kS+9oU%dm8-JATa#4juS@$a8ESw>#a>U{r`?Wdy8NXa5z|4+HPLT$!J}<84 zys7+)hE&1+5k0tP=<3(PN#*iVUPBZ|dq7ADu59zWVzc_)m`K2a;rqh=+6y z`L7w%|9MMb6B)REyfe+B{EOb{Ta#s^K1TdH1NHY17G)lKSJLFvCX&F z=WJ`sv*kNhz0FPe1s`m5Fc9*Ovm0*bbNiN-j`tMdxO{1cv;(>A3gN&hFe3ElFjqY4 z=;asuo3h82@!#qB068P$<*$nqr{%61dxs1;(}_&dw6XhP6yB3You?0(Ri8{JGU84; zo9}RW>mB!oRjB+=pmf487_QU+r6#SXJ63IPzf{N>igmZVPN|VQ3RAMrsQ*`0(?Rvs zaKe$F+BWuU1K2E4Lwvg?QpV=?e1uJ)QVr2)c#S7A5URxCvLZvLR*aoC85HBR-vbjW z4f!ml>q`kqIHQ<{0jOp~E z9opLdQ{3W33!xg34u$CR`z8NyT*W0z)DHNRu?9imiEg3S!c0vG(n~f+BEdPe%BdJO1Vu9ggEBxHuE8 zZuK8B+aML_X;Uby-|nZlvJz?4#hyN6{5fJVdD2`1HeqrSR`#T^+YDD_vbtGRzWU5( z*X*7xw+&Dsu?yK|_(mVget3V4;{WDz4o;XJyMZoq4upokQXMYWoy?wuRUBRI3|P0= zJ>LU0Ds7o~T`zxDgKZ3UJN-_Y=c3=*4|iTjK)|-Qw&437`?P-)K+xXOAFuyCwRjZV z1n=&CWigSqn5%A@p%D$;z1@8K#N~d<0R;&uAw{WKWFZs!l?wHBv4f#QJTE5q?Kj_s zxA-5agx?CJ-nlxw%?kZruvb6xdOIF8eLZ@3jk?7TYnqq=STED+{M6=ZC>Z293fiz}I*nEFrG`-x9X#>cmMW5TPgu1BbQLm5&c!lSvMyJLCq z;-gTjiMiHnO0ih$PP-YMwSnjulR-T)kwI4wou*xw-*Y1=mCY?{Ad6OQK`$LlNrpmW+ia2q;w8{!b>AKxb1+WpSG z<8q%RXmHB#);lhWjF2GKCo*HR@OgI#4fK>G81UBfyzG=kA(uhos)F0&`h5Q^*lIa7 zf56)jUkW}iLTT-s3JO=Z4}JjL!waYNh=u2@G+apVE1Ylj1a03vu1um#V;uiv>BhwJ z5265HOnz*&+}dZv*(M%EkdX>7mmX0qwZC5fRoIZstAopKArM01<3GhKa&ow@Y zG7j%RB%t0&=P_<}wccLrytyB#=!j`9kjo-=t@3MOa!t*}9Fhmd&{H~ zmJa`p7+Sd@2Ok0s8*xC<&!noBrek1x>+lW1r`viT76Lp6Egx_W7=B?D#&Gq zpq<^;3x>Nkh%?Lq{{~}$S_|!By-t@fv(5($mB(5_kHfSwBF+Fn0iL%XpZgv@>wJPi z5KLUG(R!A_-8#|+k06e@bqPau?|ea;g^i^r=m$op!x& z-{TH0YWA5Kwl$;Klb)E;qT_+SFA_hEfDe`)BZJ2+v@h;Xc1-Y)Ih#~0;!SCL8}M&H zLNJL)us~EMPyXZ8j#Nef9Garez3o^uQ=6ytn9%L0x{uNt*9FQw{73hp>AhH&z&?fG zAK=%_Sm%^;C14L^QtC^FbP zc066Qvv@xmKuv_uN2k|mB@+b`40x|#0_1BHA+OC+D>iVgeoZ;y!C@5%*6(sKpzb;E z6T%x1OEd&0Vcn|%I*i=eWHX-DToKd=w6p+2C}kRLf!>6?V#mFDAK6X9v8oX;njG3h zZCbAagZnpbUo)={Cd4zi>kEJ@4N}R-Ujr!TYSpFuF6Rmbd9OURYUT;dYjJ|$K{ z!DDjTV^q5^j_z_Y)8dl1+oy9>QGpE-DcqO!;V84ZQVL6}O_rE~g{^io^wFuF;S_=p z&rEV)90TvB*E5|+K%Wm_;!B`&4u?jLT;={8sqOgF#u=-{<)J;EWs8ca(8Iv+xDm|u zfGh8zC7fE_q0N?jlg|TwVJU>srp57Z|r`bGyfF%TW#DqlXbPghihEq zXBxG}0ED~J`yROt30(5CunN#3v(dOU-!M05X#IEftNF7mAacsm@PfI3`BA=A*-bO- z#~I$y=_@U;1}2TP?5{O~&}UhJFU`y2#3jby4{*+6)_K;nN@%&&?k1nK%)1B43Y~f( zdSa(vF8-y><6*?~{zKwK9JyTF#9<4SB~Irf2T!1<5yKCv2?PI#zRyeN%WVZz$5(B_ z1LXil0JKgy-qq4@P#fzXM30GOlhGHc`@Zca^X~rc+PtdD0|^$~LjS5(&6f7>)xLDD z*lAqAz*$00i`uh_4%3zPvlU%Xfcs|mRq?khUJscBy$i|#KmAHavdTu9yRv~K8+!?y z$f`T6rf~#3LR(Wot>ydv{(?`1@ zdv?ObFx^O)jqqq@T0Kd8M0nKgC)=lM^rEmQ%Wy+IDIAX+DaEJHD!?&53*&0wK35Hx z=foW_NN8VeM%S~PIcLEgu9l#?8*4)Q0j_Qs`-tNoRap$XojQ}Le82&YII@fL{o;zJ z?b#1Lu1g1P4Xfg9q8e}Yj+xvr}akd`e} zXhsYhUUt+86tY;iTe1gq~{jyxg0foBat*&b- z{NtIymEaB$72deo>*?CKd&I)+dbi%&aXD}x)yb}z*W*Fqp1nq4mBS8iZ?n&8)HO~f zlWUADJYdsT)o6d0O6nNc(!Efj zn`Hat@~ikE6jqqJz1Ak3JL4ccmu@>xbu4 zBfkA{xSikVE1gEN_bx}zWh+3Ra&zc>NFE&7J8(NHDrMufoFA1P7)(8AJ`0n1alO(i z;oy7T-nT>qrOHL(Uju)u*C0MEmgS5_RRi%8>#031O|xLX!MKS9l+N4y#+ARDq4nnI2{NNT4*qrRp_?IqQ~i(8m89g+W)0jq^<|QM!H$ zA)JwkxaloLj6}qh3kmg`B-zmAZXots>E5vE%T?ci(z`t7Yp)U*?XK8sN(&|t524L` z8q^$?%iNvFN}a{~F-ZN~tAAB!mi3>jxBpxN5tBvJsw?UkmFEp;+#R=z zyS}`i@Oc>va`oamv-RSMwJy4Y=fG8{#v#0-{&2l6e%r#^cJHQjpD*GIj+7`SKSVwU zPa?QNy58%~;1QFTo~5bvAftt2spm^mNyc&2)g>oFQA9^L)8Z|EN+(Gy;7;Dn)%5FS%#% zcb}z;_C?o|C}rWl*>K%ZN|n*Kht{3ZB2vHB}7#PTD+eWD}3>cBUqp9nkE3X@E7@5Xn$B zi9z+pvBpSiIM8Pw!XT5hyn5q+N%uWm{@C(M`~FA?--U3q&xz|Nh|{o4(8!nu*zRI_ zM0h{dYOD!Yqf~4J*OX6e1ck90puLl3l_|7Ew1L-hV0R|&vDMWbF5vm_=EcHV zz2C`z=pM#S=d*lj?UoP*34HFii^N>b`=sY&I>9X?`Ut%{m9l3Vis8a)9HGJ-zrZ4j zfHrMh&S~N57%C+(^~dp9Uv?%8bZ|mnAxDI1DDpwMNO2gj{V3$=#KyQ>X;))ey)r?i zlppQb`|Ua+vwl$5 z+|zBqJh>#<)=|^fJ0#IiyzihLS=g}Q^wlaqvrx5pu&_ufm^+PLTf?U1Ciy@HvhD{n zL;@SaO8a7CVuSa6-`E1)En8OU^R+Vp@jA2BTB`B(R-^lv2H1E~)tdp>g+NeZ8^xo$BZO^JZ@*`O;_9PkV|O~R`0(nJGo(}igBBbUMS;o)b6rhj$|EAf z>$9QTuf@^DrJOIF@^XecU@J#amBiWWtLlq_07_-30JT1tzGrrB1{z6}`(!R(CBD(V z`X4YERQf#i9o&9z0aWAC2AJ*}tn)PO9-;Ht`BF$|9W39p-h1F0stv=VL0nz2n3~~k zphyh~J~>>jl{E@lzp_6?_L{e+-7r)LrA2i#&J@^p_*tt#6niQB-H8lPh2TA?%Z1Yv zhdgoOH^nPUyk-t)P`s-a<&S5n z)MsfZ2udHGVs6On?`(@mTDyw-lCzqBha6q)P}FyWJRIq$;(Wg4;)SHH*T6-vEuTRh(NEZaX5kcGfd=z^fwO2-gLJcUguAHTPUHw zGA7@s^R=-R&1#e50| zgSyxrwQ@E=mdoBwmLP^tw}C>I_iA9ibOtdz+#5LL8Z0$0wtX$+gPfj7t4(fIb(w); zUb!52Bi)hZ>SKJ)Q<{ehlAXWhd)R+$=L71P1Y+L!h z*bB#o3%v2RSuf1VEgLllvtS*+L)f{)+oIzXqSXb)>ya?V^c@OV7*ZLN%8jf<+bctdM^W_%Q4d|Q~0mE3$n?KDtC`t+#{FdQ}8 zHt8uB!GGc{yi#7^o;mGx7uSZ&tAdbtK5=E+V5?dg=!g1ZKB2T$nRy=8Qt`Ia*r@q) zaP}s_X86~hi%Zz7{#6#6_@|4p;F+z4!P@!Lo1npcF^n4+uU=e*GVaeWk zYf{Em#xy27eJ&QpJZyHyN8M~1MwYk_wtwv!8FFx z2C#maun0|N7{>k4?<+G_%!syn8BuB|9Uar-L_ z)w^00-Oix%&CSo}qBw}{Csv$TBYm0W_sXr@*(O;`wZC-3nzS~lKECHMnN!u4x*o3s8wHtmt+BgIKbeyRL@=2J zjv-Ib%TCyu>D<)Kb^gXfWPiimdXGls`Iu%sbz{2M8#Ytj9@2&7Ba8P!7aOWxw~nJX zmmQVoHVghm&;+*=i=5@$G21Z60-B{ z-B3##;*93Kc+LX!&`%7mc=}nBMcJ-dC;N_F*>3?VX&S++sN{cuy6FnW^No2@nK2p( zhthPRF3NY84Sg;MXXodpTkx2w=OCrEls`EMxq516S#XU@H8z&ve_UIkcf4kRH*Rg> zVVX9xN%rA?VDYvtxw0_1mOF z$nS&fh%6xXg7PJHJ{U*799wY2} zk87*Q#2o-3AjO;G zTowVRKM&Pj8q23HPP|R9!_r@f9>UBkx8v+$*>PW9HHLMg(*dAPP4OYqli7kXl2gUL z7@=vsnagl#S+?+hAm0CVFpAz|it8GI?D_$7)Q^rK}EnRY}VYNd5M-F~m z7IiWAM{&IsX3fRhb8dN2)bcmeKV|gAquAEX1QQWurjtuIM!c0}4=Bq5+kJ8TN^IWq zuU5QBt{qx!IKMMVcb=iW?pil`Z}fN{7B6W9aua`jnUYIOMDj_nOr-n5rI-P~BwuJbVS{zLg9d{h#tr04GoAoAEs?a54hB722tI#F!pxEd9XT;oxTISJXO03`YDLh;c_Ipz$=5l%(I42F z)1S$|60^jvlS+EX#(~6q>$8S%E&s%Y3d zQFQEM(30&u#V=^X1_lr2k8$A^bF6JqR!`2mn40t4-ft{bylVq4OROU&YLmBufZ z;L*8V*z|rS^X=86X*n~JOz#mU^nC>=@k<1J1im{eDh;25}rDwohgYgC-&jlPN7aIcT@8m zm&PN6pmxe1^E(5(ICz|)wvegKDf%jr_}_H6l5_dcbRv;cKi3}?@n zdRxuGkh_)U1!?V2^m?8qa7{;dr5lz1r=aCZB4=7d8DB;5sSH2-quodMB|{%P^Ef;} zxySOYdMO-Vn3yMjJXftvN%5t()1AX0%DD~5>tgs(jMge+#T^k?=aE^BnL)U3V(Bdb z*<`&2J)Ff)_}=*TbDu#iFJHAo2S3c*;3es4$!3eEUR!PC`(m@V3M!gq!-Xv+T~|l7 z8de_pgfn^+$uBPkeHD{LzqVKKF^#D@QI1JdtH_1ryz*-m9mC3TCJ0un)dzB*znccwH!k#YJ^d zh9A375(@Bw^&k}IY2SIyZyKd6F`HQtI1&Pv!J1DC6G#`gK!sKPrr3 z=qkO~go{LqGrk1`oPIiab&jy<-69pQy(zKwzRCVaj4o(=TC~?NQ*&Fbi#gRwK!qQEnaqv|ZZunYS@)qQ#iN`utMNc>AfY9S1iJiQWdhn0f_=3_Tz^8DTW@RBidXm0 z{|@K5LEVvLyT$2X?EEKg|I9>cna594Ja0yJ9!oQy%WJQ_o!h%06em-B0RJZ?=hQqS zA-8igm>2g;78R>if}~4;oV2W#kX~np_GmR~Bv<$fYTX~{5t-i=bS12uHX`FF)-OxO z%>(nm{nL!RejXo|;~ImrqYh^;7S92>Cjm{pY=i?K=G;QkDU;R{4I5t#^~~ES!WW7I>qp8gCFd7C$_Ve~qg9K! z^NlzkHRa=HXOhXMgAh^irr-ov$HPSx;E_Ms$9iPLlZ^HA2slG2&U$&nl3Kc(E6zWl z(PYk*A5H-^?vD1xT5WSH9I*AU25EJ?4t*6)8@l<@swYjG2d3J4(>$Ej8g~c1y8CaP z6>=TE$LoHcCcU~to-R=_IYE2bYpG*zZow9gMGndIjfZOgZFR6738+26} zSPzc-r%lCeZw}xiXIQ<^vUse4%K4=5|1>JF+-J@>ld15?f>>}ce&Ka3Zt%Mdh6rw~_OGUiVB>^<30I&b_ZxT@ay zAt4|>(bddA_m|2g8GAVSG0JY?`*|7zCg5e)P>Kgc%%}O-GK@f@-PU$2s1eOl?r& z+9e6Lb02s({mz-aI=Z5k6Qt$VXpHh3Ursni&QW(^C8ZY!6g+&DIWoA#o&V%-f=d!p zKZo_Z%!-qJzFn?j3hyX0TEE?C#(V7@$=&M%Uc&gcpnr*JL0i)K%px6TE?bA=1B!~_ zQ0UL+s3Mm=+S-wK0w)dU(9f& zlHHH=^b&Hik+LX@dmZX^MOho(5Fz;;Du1ptgRV}v!|IzW?4m18<&=e5s5vbarUUX} znGM_8ZZf>dLm+Zn`+6vlj)vR9_7{36@_X5q2fBq>V5d4?`LVtSS7+Fec4b;yUYOlR zQEHj)4}+XR!2Jm!IqKx{Qncu_QL$=<`JR9v|S&c zg596^nHp-Gt|@7i?*s!XN&|8^;u z?`T=mP8i%S6GzTQfpN{mE|bFiM{@I5S$SpdKqLx@?)iGl$dHqC76lYG4N&4Z0C;XX zk=m}5jkj`@GPp-s^jK>&W#gQSP#1;COTghO(Nw51;+@x!%LHECDDXz+Xv^KS3EYxg z&gR>3O=y1qU#*u2MUT$-`aQ_66zhL9UU!itxh$>_SpseeRfdd$G28`t6oA$a&|VA@!*&h)QETMds@TP-+`Yh<2=h zNHjZWvqIpa8k>tqm<2oOES?D1XbhS%s9EC5lw7p4cw&>>0 zx5VxzbHAVcerVNEeVj72g9su@BN=$L4w_yBH91yE4fF;1yVA6r9gKW~iHZ2_8}4Co zLa^!fHgJ#+@)eYl9$j=$soR)pj*iVZ>@~U}lwPgaI>)y?HIM^)!-IG|mo)_7;e%6sNY*f~}+$yY`VqNlF+&#$u@qwYF#&L)RI}Dil zUTYjo;)NQrI1uxh%kVW8x#D$4j9(X#r<)_ayM|uq|$3rZM+@o_RW= za+f`|pSU$VT_azubqSJQx+qt9>e^1@z6w~N*apTYnIpp6~EPa=SRh5}rI7=4rwXiW)Uf$H|{fx;$RBJ}j>1ipe&_%;c zXn-Mn)t6(=&EPMWc zp@Xw-Fc4Jl$!oJMiWn9nA{4MP$ODz3OESB8&B0w+<}HHDsgB#y-inIa*gP^%u& zyzch2g(GBI1{NN+UAvA?OJn}U; z5LRNGAV4e9NRN|k}QBHjhV(4=Wuw!ZL7qvPm zh)ndQG~2}ITp;e_zWTPs1iwfeLyIgwa$XuVA8@UX7PO){TNtBHaVlrZ14_B?ov%HX)y zJf&Y(8D2cib<0;jtzitX#PG9@t`X|d_t!X2BWG)Mau-GSm^kW8z`^Rve6_S!L+c+m ziDuLxnIj9xHOj!2!oH-3^ORlPS%VcdcWWub(5|kbx8{#ToKNIcnMEQjkt^F=LzgSk z?=ty{gCP|0xi0UU`2Ms{!4E}$KN%;?f`KEM#FQ0RGJuzN3cOW%V>O=$Qcyq&Urm{| znbu^#na=sq(To8;$JRQBPS0m_-f*Wyl3<2aE~(e|v(jc#`)0!w9c8MZkZ5L5t&2{F z09x??1F(;|wL(st3$YN8TAe>^Ze3EBF$QIiT7n|bU8#FO_xoJeDK|$HzND$S%9$pd zMWQ744Q&`My)=$4JL3B%4l1;8Qb(nmDIMO}6@2|MaiFnel0li_jKi+kswDad+N}dP z=$fySENB0)8+t4Jb>apzck^M0CnoMjFbmtE+PHkvACld_9Y5zbaDzteXCNZ3AUO9& zlD<%;6jrCUvpnlx>%`9(bPv=l>9ypK#IMMQY}1S0Ci2psEFSHKlhD&hyBpe}81*hD zxr6!s`}asm8U8cmmiP58)jVaU@Fd-OhiS{$u4MB>7~WlLwCfOv(@)o|E%N;chOO6L zwjYLWkl;nDJDLndV~A0FBFJKMQ%>=X9eU6wm)HOgIzra;uHW099u^kD?}P%cUenu%l%RtsIJFhT{!Qr+7Jb+kcRhboHIB1tiJ? zFo`YPe4-u_@St;|DXcs4_7SGPq6`uFDSi9?8+HX6YFaUAui(f`ppwMLsg~IYL2u~G zR0CBLQurPCJYb11qRzVEkEI<>-IVSajL36s{}TtN3bZ!x;gQF|PR^+N5E`vs%^yYA z<{`kMRNT`?A4gXdqeeuWZ{mzkL+<{X2rTy^68C3)p1<}h!iCg)mY>LzsMR-Njr4T(%e8M5|W^@ zA^qB4*%fte6zpvq<18UZqd&abvvSQGlfU8*3QmI-7Sv$IDH9@P_cTS%^B`9H}B& z$V9EN0bGE+@kpVR_>Lc}sC^Z8y7DP8THeu&Lu~*2nTimLqqk0nJAq~cP*tH$iCk_{ zbSkoZlf~u@VmO^DZrBq6hETaodQ78MA!eQ^7?{%b>|0Od(*V>FU#QrS-zChc4*d@o z05cgmII1VOe$<-VG>r=1V75e$7IoKQUC#KOWxt^rS3dl#J zv?8X_*SxMWEpnNZF#&G^emM$)auz+Q-O-_w`~5cpYja)b>IIzWXp5MR2|YVEqsH&X zGg>S)&dj&DZ1o&(W7XJW$qG6SO*~vtAwhV z47vPfFe@yk51o(cLM}M-?aJ=(o)(q;^@}Hhzpdt}Eq?oWCZ@tb5`QsR#NZ4UYc4yt zJRhVWV%v~XO?RO^CEG|V2v>2Q7+_1!&GGHfT$!=PHSCLkSvcHGSW~9(F-*N5QoMoh zJ!+R)8A7Mi&+SM~Z>etIP8lw~qct3Y&$XN8+a~jD^&n!oz+n>Az&+uO`;)6KqdM(U zN6tmow9_9^z?uYnVg$uG2zCa4pg7NwORXb{_GihLsh(WDxxQBHB^gu%27|OZ^w9(_ zRBGFmT;KR&7wA{roGHA|lKwlFbsMnb$aZ39Lmh>ZD z{fMy8M@2NbOtIuT=c>SJb>71Y=U;HBh-H`=c^jHvl|v?Y)KZCEG!i$0$+l2gj-H6) zvR_r76fby@((L$qS5R^uj;od<_}mQEMl5GjcouBx-!%EJ0hsuJR|hzR6j? zecrOPh$OPM;epLnqBXt3Msr?nT|=8{j_fNuY~-a@SUYEsV!lEv-VZu^M^XoTR4610 z(GN}x)aTQUXBUl{-DWm}7lwd1L4+$*vvCVQu|K3kuxY{2- zewZN0@w1adVg|VKS*7G2j5PZQ%1Q@A`bc!Tqzz=&E!_5ecmZvA8$A$gru?GgL+zcA z;)t96UZ~SvyMNG(DOGG_+>M1YP84}#N7k}fBEGrPUB>ye=a@R4S{qF;`F$Haz}!hk zR{daH?w(voL?~hN#ThFTV9Q6(W}x{(A~Le>ZmTUiA(Yv<{P_<xUl zelQ3SjddJ3o5ilhfGu>4u>8nSlug)`x{B<&f`MWW{OR7wqU|XIoo>&q#QA zI^cJK9r2Qaw<;#z#el-})uHjDBi3^wQ;-tIl%T?^hUQvhg(3l${|$^{0u62ivY zWr&}M8Yw-5PvYa;1p+~}d{hGC;?b`44Cbg^HalU044u~Q?aOuWHTFl zat+>V%~Xw(=3myQYdiz9MKwg zmIpPkCV=duIUDBr;9QwZQiqSY&9L}${DX%D7LiF}-GT`3+gItWdQW3ZU&w`3WtnYw z=&jyh^RlP1U2u)A$Bx!yCGEL~AgaJw%S5C6lfI^nW%;-KZy$^cw;jc7F>iX9;x`C} zcuXcX7U+<=_YF=6w&{qR@sg`=2=$g?*l-T6NW)E+6S6{31Nccg{B|pOmvO^QOG2d; z8nA}K_jpw_V(!uh-DPD%>psawEhEt9q+=lt^IwXdd}Wwb&x~B<7|(`eBGhbZM5nc! zP@vPn0i9Qj?3c%kDa5ikX9p|PoRhnrNz^_VoO$>7#d_M!zO#W*sl{1gMoYYas0foj z=l%Qp)Ew258Js1YvGj157xk!_R{Eq2ZVCFYYV{!ig!TTk2hlb4>Rl>~QjH|OIi2pc zD60>ZBtq`k+?Jp>1ox#x#^!THk{)fRS<4XM;6-kT{cGk^xpF7ho&ZJH^YIO;M>KdK zMpHrzD{RVmbevI6>u$XRj1|4gy8f+&x%Tq|9A^L8ju(w0S91e|9^54XS1-dlhrywt zP;ND?`Rp}{ZyLCL98zVeMfaVsLEMJhAw7y|=U97T<=f+v?heEy8ek_pfn45lEAL@B z{;3e>$i!spO_PV?Vzb?gzM$@eTQU7HTtMJQ5f5VXS!KiKPolLJ!v1H)`u}0Qhj<3F*U7U}8aA&m7L4;)g9B%0m zd^qm>{7#OLMIx?9Xu{~M(T`$qq0WnF{Og$>FEQuT&gky54u+_aCk5>=tB|dPw<}oL z-myj1dSyKok;0ZN0Sl153(J6=cgf#GWY{#9Smm5Q&}tfVverNuQ2JSaNecJ)2%$KY ze6Vc{@D=1}HdEy|P*;qY6rF6`t1W2J@zSAZ@u)rSxq1}hFWCXs8+ZL(883{i=x|0? zR3-+w+bISV8cwNPvAau(A;Pz$)0`3RP@NmiB4&Llv`A1yYz55SeopL&Ss0hVK;3FJ z*4AavAH6!4J)im8c3UB*jwR-}fQ5>r#Z6EcfWBd1Wp2xuZGC{KoQo7F{^r0@xvRly zT~O8f!}FC4H{CoW_uDRmAt?|_m50k}>KQsiCwnY*dF|@_?7=V$V@ZlP*sNF-RFwKyh7T+##;ls?`(lLDd-dDJsY=OAynoTw`K`p5N#p}&`TW){ggly#ukx5bjCo!Y$z%Y zA#to>6M>H`;qFn0t#3M2>4&4@nq4}@hQDhQ`>}#6PPceMgZ9_i^(VE^x&>KKbVa39 ztwxPY7q*>{FtH$PFcxMEJ&`&wq%~oX?t~wS<@e$0e)B)Tcwopte`fZUp=Nexyg6 z={qV{kIh`l8ej)v*WN0OaTWqbcgV)ZMx+?#2wYY%Q(NMg=AHssWs=MCD|8B{m9}HS z0d;i4`-+amR@^_|92nZySqTB)7v`WE0WQr*0@LR^RyoG-Ud^hc)fnK}#P2}wwaI;Q z%DIZbx%NzBT*c7(lmC_I@dliu6MFkcOTgcoPLWY55E)`WyL(XRX7GgcuT&afC~q0Y zR`9<)rpl%9NH*H;h1GS+l{|!Qhop0R$N@>3D&Tg>OlRWKUpaOQpjcc6Ssr}`HnbXT zC&x?G5F)qZh`v#7*ny04g1vmL zCz0GgU1^@!C3*kX4giks5E0S$enyWv5b~TI0ii>o!~VLdgW~fWGc`3iZ-+2 zx9=-JIfX*=y@*|jU2lP>W*2*YPUyca;^&a?PZ!6KMD_%rfbd@@uRjl9e~erdvU^A5 z=i(~0nn1ecQr)NMpFqL%-1awZQDA3?BvL|r9%qSg{>P8(>;x~*^hH-IZnu;5!T~?# z;N(a=V{&|IeFRRYtiBUPwV%#tV^D))ZCgG_%|K2fN!UYP-SC!2V{k?q=!SxA1 z71jXyYs-dW|4i;5slI<@{Kk9)I{kx{%5m7O3b*_g4Sxy**``cFBDp5-DSJX z=31t4*ie8N3-%RD#Y=(vE-V80LGbh~fUNi&{sfxuzgYC`AZs5T65@Zsvtwcfa3rLH z{_zq5$HG5t`4x^+GlzdakN!O?fB?uePB)dCeqhdsYZu7P0%8`64apEemh+WiQt_t> zFe5~+ntHQ4GG@}Z?wlu8xVMKKCwQv)F2_suN>bDfTIYs>T z`;hSl?R7N1*+E}4ZxnwEfaUNYK=*-Hx2gV@jsJE;o(uT-{63W8ho<5$a;$6;hkE$Z z`1gmUs+|HB>v2To*Nwmgji0VD-&Uy$?fnbnQU{uP1`3@kv+X;^r#8hrsl2kC$iEC1c(KO@2Rb~~h7i?%6+!-< z%3l_u0|Of6=Z!|G6(kn+C@3kp?z(wj8OJQ{m_}v(vAm2ZXeg0h8f&s(t_NKlv8not zOctH4eZi=9yP|`H^_}S4s3A#^0;9=iUFasybO;aTxE3B>ppHRn&32^G?nNrQxsb!r zbasQy7BccQu=jBLg7KAnDwQv0^auf~nFPOlMv;y*`it1kP(1NIS|sKJn9=UO!BMv( zI!Xe}Kf`s_1K(UmfOl+4+=C)#YOuaQj;B$)|Mmg(!++hT{W%;ZQgYZME;pPE<(x7sfJX4oK$X)v9z1q)se=5WbCln z?Z+FOIG#psQn|GGY&sT}xN$z6mO{=zUJt2@2iG?rzw7M_6tm@APLG9B7!T&cOi|s& ze|85*_KSFgqT{M)T-4Qv?r9QVj{G1=w|`#g@88s?1Pz+Kxt1|!w!6iBVKRcq zlZ;I{oGvp}usQ-&7+77^sVl-w@L3;8U@cx@o3)#QizSiFmLKT@zu5XDAptpKbzj*i zBlnLBS>GWBI{KkLC7yr0&7VnoZvdh>B8S^(A~u^@c1>`>D^v9H^0NNOa+)l-U90Os zOQZU70~sfroln|tnSPjG{|`o(0{)+8{Ow?aS|M9>i*kw7OG(M~&%$VpZ|g>vY|(5< z|2jx^>ws_euKpMp{x5$F+=39GLRD?Bs6Byy?b&lnBH+`SU386<|Lgkx{!I>10Z9D< zL`ks!GPVEyxK-I-cEEmCTq5;9jrre<0vx0O5>oIOmB{or-}n1Nf18-*V1ef(8fp@j zIQ&cL-(2axKl`6g>m$TbERr!8DI#Hz`t10y^o;-4frIylT^X;0jy3b$R^{#=ELvJ$*A@SbhPiS^xYwEWztlrLbu%@r*A-f!~zoukHSy!vVe@#kuvP)X9AYoImH&r%5jYkB zzURD)?&_m@t?s69^_@=MYJBzhA-w3o5HBHGW<_GzvRi#n4@j|x>AKh6+T#-`%mWvbp}WKWb* zu!UnAh=}2$=N842HvXQ_GxZ~wPzXigN2o@dIPWH(!;;xszAk z3yA4&i}7>e-+Mz_H-jiwD-H|~)YKNq+a`z0*ba}L=6si6h%(c#|x&d zN0AOcky6i$Ws2Qs+dOm0F4`o&TI%{Qqx9rx66&!;4NUOMF9WQ|yoSEW>fwXE>{Z)$ zoNI6vBA|3vDDnid_|;#!mW@}Wa1LAGr7v%B)@dH+q|lfsc-u6HO*!di*+ny&GR^=j)4UEow88Gac>%@=;PJl~jfsrQUBwbwoI;@*9S!EwE1tOukN)to%zX{_c~ z*l=_*FL}k&pETP?=Ch0zTLa6UnbvDaa5{vV&?)zN_;#5uiDwi3Cy6H1Pm@U|GTOOGlG{oLshxrzi11OmK%tj)dU{?{{n&I~u#+n=vSV z&PFD&;l;=4UhyNZTVmIB_9Z)ODG3@)aZ#iesz0pp3t5JBLoeI4@A=LQlkEl*>l4{2 z^U9~|DiuA)+F*5re1M8oM!p-(!RNw6=}7{RQY5O?1RYGD4If=&k^Hz{cN;NI`?_6k zP;=SsfPF5gVk?apDW~hzFH#-YnJrprZ}wU&pA5O!EJFs?7lU&5!{y88l1^eGCG|P9 z0!rYCcDl^abti`_Hjgm+=-%{5^ziA7lPbN(Z{Y5@=5Ephdx(Tj=BcFjB(o~QuHF`F z#siEIzANQ3FyVjI`S`i)o@MltL)9Zn>>ju_-imyTR}rhUCHCNn-pg1H+LHVRa{hNM#+qSGqDkjhcb4tLpCNe#SoIHpTzJ*PI_B zML}+=dfT)#*qCVd6&8+b$Yk$g*2d1-iP&`cW|(!CVw_Sa+ER<>5fNeJG7`zl+ltsQ z#QubX&dsZqVd`?V-nPk*xKu!!e_+lS;GZAhGonBd|7kX5Sl9+J(G(J_EOPhSuh3PI zjkSCL6XCi#Ok$m7e@h%UaZ7Xr=htvDhV%narnkf4+c2E_a+sDlHZi`+VtGW0UH1yA zJe;9;fZ%7?hJu~H$ngk<3s9(hJ+{zT`+*rhY#8D#HeJg0F&>DyUWeS*yoQQRSw!qu z`e84ImR>ps8q_=$XYhzL?8ITL<6AlUnKqsPN0F%1QmlWoL)j>{Jm z#vx|NMDzoNv4gA5lkPE+I7o1PODpx4{Y<}v^_ew(DwbBwKrz0n{;GnoQ;a5F_%^KY zlQKrF?WEwWt>|>hiM9GuVilIM=6yE;vTnjyw|V$0cMPqJH+xMX93-Ip+0;|V1~NOoXb>t^|f39;49*(Xlw zA=za^TgLhjR0z(N@L&VXTzE?!_w4?y7XIUGE4A70)Ui$nG341JZB8Z|SpqZgUWiE0 z*|!wp$3!7B67Z=wSaPGHViyU<3-2t9s**;>7#R1%0Altm!xYq6FZ%}dRmpL2y@(Mq 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 S4SyKu>x1}5v0@QjzyAj#B5u