├── NOTICE ├── cdk_app └── k3s-bootstrap │ ├── .npmignore │ ├── .gitignore │ ├── jest.config.js │ ├── test │ └── k3s-bootstrap.test.ts │ ├── package.json │ ├── cdk.json │ ├── tsconfig.json │ ├── bin │ └── k3s-bootstrap.ts │ └── lib │ └── k3s-bootstrap-stack.ts ├── CODE_OF_CONDUCT.md ├── LICENSE ├── deployment ├── redis-deployment.yaml ├── greengrass-v2-deployment-template.yaml └── greengrass-v2-deployment.yaml ├── device └── bootstrapping │ ├── install_k3s.sh │ └── bootstrap.sh ├── docker ├── Dockerfile-arm └── greengrass-entrypoint.sh ├── README.md └── CONTRIBUTING.md /NOTICE: -------------------------------------------------------------------------------- 1 | Containers @ Edge example 2 | Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. -------------------------------------------------------------------------------- /cdk_app/k3s-bootstrap/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /cdk_app/k3s-bootstrap/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /cdk_app/k3s-bootstrap/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. -------------------------------------------------------------------------------- /cdk_app/k3s-bootstrap/test/k3s-bootstrap.test.ts: -------------------------------------------------------------------------------- 1 | import { expect as expectCDK, matchTemplate, MatchStyle } from '@aws-cdk/assert'; 2 | import * as cdk from '@aws-cdk/core'; 3 | import * as K3SBootstrap from '../lib/k3s-bootstrap-stack'; 4 | 5 | test('Empty Stack', () => { 6 | const app = new cdk.App(); 7 | // WHEN 8 | const stack = new K3SBootstrap.K3SBootstrapStack(app, 'MyTestStack'); 9 | // THEN 10 | expectCDK(stack).to(matchTemplate({ 11 | "Resources": {} 12 | }, MatchStyle.EXACT)) 13 | }); 14 | -------------------------------------------------------------------------------- /cdk_app/k3s-bootstrap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk", 3 | "version": "0.1.0", 4 | "bin": { 5 | "cdk": "bin/cdk.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "jest": "^27.5.1", 18 | "ts-jest": "^27.1.4", 19 | "aws-cdk": "2.44.0", 20 | "ts-node": "^10.9.1", 21 | "typescript": "~3.9.7" 22 | }, 23 | "dependencies": { 24 | "aws-cdk-lib": "2.80.0", 25 | "constructs": "^10.0.0", 26 | "source-map-support": "^0.5.21" 27 | } 28 | } -------------------------------------------------------------------------------- /cdk_app/k3s-bootstrap/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/k3s-bootstrap.ts", 3 | "context": { 4 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 5 | "aws-cdk:enableDiffNoFail": "true", 6 | "@aws-cdk/core:stackRelativeExports": "true", 7 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, 8 | "@aws-cdk/aws-kms:defaultKeyPolicies": true, 9 | "@aws-cdk/aws-s3:grantWriteWithoutAcl": true, 10 | "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true, 11 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 12 | "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, 13 | "@aws-cdk/aws-lambda:recognizeVersionProps": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cdk_app/k3s-bootstrap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["cdk.out"] 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright 2021 Amazon.com, Inc. or its affiliates 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /cdk_app/k3s-bootstrap/bin/k3s-bootstrap.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from 'aws-cdk-lib'; 4 | import { K3SBootstrapStack } from '../lib/k3s-bootstrap-stack'; 5 | 6 | const app = new cdk.App(); 7 | new K3SBootstrapStack(app, 'K3SBootstrapStack', { 8 | /* If you don't specify 'env', this stack will be environment-agnostic. 9 | * Account/Region-dependent features and context lookups will not work, 10 | * but a single synthesized template can be deployed anywhere. */ 11 | 12 | /* Uncomment the next line to specialize this stack for the AWS Account 13 | * and Region that are implied by the current CLI configuration. */ 14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, 15 | 16 | /* Uncomment the next line if you know exactly what Account and Region you 17 | * want to deploy the stack to. */ 18 | // env: { account: '123456789012', region: 'us-east-1' }, 19 | 20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ 21 | }); 22 | -------------------------------------------------------------------------------- /deployment/redis-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: greengrass 5 | --- 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | namespace: greengrass 10 | name: redis-deployment 11 | labels: 12 | app: redis 13 | spec: 14 | selector: 15 | matchLabels: 16 | app: redis 17 | replicas: 1 18 | template: 19 | metadata: 20 | labels: 21 | app: redis 22 | spec: 23 | containers: 24 | - name: redis 25 | image: arm32v7/redis 26 | resources: 27 | requests: 28 | cpu: 100m 29 | memory: 100Mi 30 | ports: 31 | - containerPort: 6379 32 | volumeMounts: 33 | - mountPath: /data 34 | name: data 35 | - mountPath: /greengrass/v2 36 | name: user 37 | volumes: 38 | - name: data 39 | hostPath: 40 | path: /home/pi/redis/orders/data 41 | type: Directory 42 | - name: user 43 | hostPath: 44 | path: /home/pi/greengrass/v2 45 | type: Directory 46 | -------------------------------------------------------------------------------- /device/bootstrapping/install_k3s.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Install k3s 4 | 5 | K3S_EXISTS=false 6 | K3S_RUNNING=`systemctl show -p SubState --value k3s.service` 7 | 8 | FILE=/usr/local/bin/k3s 9 | if test -f "$FILE"; then 10 | echo "$FILE exists. k3s is installed" 11 | K3S_EXISTS=true 12 | fi 13 | 14 | if [ "$K3S_EXISTS" = false ] ; then 15 | export K3S_KUBECONFIG_MODE="644" 16 | sudo -E curl -sfL https://get.k3s.io | sh - 17 | else 18 | if [ "$K3S_RUNNING" = false ] ; then 19 | echo "Restarting service k3s" 20 | sudo systemctl restart k3s 21 | else 22 | echo "k3s is already running" 23 | fi 24 | fi 25 | 26 | # k3s now is running 27 | # Now we're writing the current IP to SSM Parameter Store 28 | # And kubeconfig to S3 bucket 29 | 30 | ipv4=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1) 31 | aws ssm put-parameter --name "/k3s/kubernetes/ip" --type "String" --value $ipv4 --overwrite 32 | 33 | sed "s/127.0.0.1/$ipv4/" /etc/rancher/k3s/k3s.yaml > ~/kubeconfig-k3s 34 | 35 | S3_BUCKET=`aws ssm get-parameters --names "/k3s/kubernetes/s3-bucket" | jq .Parameters[].Value | sed 's/\"//g'` 36 | aws s3 cp ~/kubeconfig-k3s s3://$S3_BUCKET/kubeconfig-k3s 37 | -------------------------------------------------------------------------------- /docker/Dockerfile-arm: -------------------------------------------------------------------------------- 1 | FROM arm32v7/ubuntu:18.04 2 | 3 | RUN apt-get update \ 4 | && apt-get --force-yes -y upgrade 5 | 6 | ENV GREENGRASS_RELEASE_VERSION=2.1.0 7 | ENV GREENGRASS_ZIP_FILE=greengrass-${GREENGRASS_RELEASE_VERSION}.zip 8 | ENV GREENGRASS_RELEASE_URI=https://d2s8p88vqu9w66.cloudfront.net/releases/${GREENGRASS_ZIP_FILE} 9 | ENV GREENGRASS_ZIP_SHA256=${GREENGRASS_ZIP_FILE}.sha256 10 | 11 | RUN apt-get install -y openjdk-11-jdk python3-pip tar unzip wget sudo procps \ 12 | && wget $GREENGRASS_RELEASE_URI \ 13 | && mkdir -p /opt/greengrassv2 /greengrass/v2 && unzip $GREENGRASS_ZIP_FILE -d /opt/greengrassv2 && rm greengrass-2.1.0.zip \ 14 | && rm -rf /var/lib/apt/lists/* 15 | 16 | # Set up Greengrass v2 execution parameters 17 | ENV GGC_ROOT_PATH=/greengrass/v2 \ 18 | TINI_KILL_PROCESS_GROUP=1 \ 19 | PROVISION=false \ 20 | TES_ROLE_NAME=default_tes_role_name \ 21 | TES_ROLE_ALIAS_NAME=default_tes_role_alias_name \ 22 | COMPONENT_DEFAULT_USER=default_component_user \ 23 | DEPLOY_DEV_TOOLS=false \ 24 | LOG_LEVEL=$LOG_LEVEL \ 25 | INIT_CONFIG=$INIT_CONFIG 26 | 27 | RUN groupadd --gid 998 ggc_group && useradd --uid 999 --gid ggc_group --shell /bin/bash --create-home ggc_user 28 | 29 | # Entrypoint script to install and run Greengrass 30 | COPY "greengrass-entrypoint.sh" / 31 | 32 | RUN env && chmod +x /greengrass-entrypoint.sh 33 | 34 | CMD ["sh", "/greengrass-entrypoint.sh" ] 35 | 36 | # Expose port to subscribe to MQTT messages, network port 37 | EXPOSE 8883 38 | -------------------------------------------------------------------------------- /deployment/greengrass-v2-deployment-template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: greengrass 5 | --- 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | namespace: greengrass 10 | name: greengrass-deployment 11 | spec: 12 | selector: 13 | matchLabels: 14 | app: greengrass 15 | replicas: 1 16 | template: 17 | metadata: 18 | labels: 19 | app: greengrass 20 | spec: 21 | containers: 22 | - name: greengrass 23 | image: public.ecr.aws/f3r7z4u4/ggv2-arm:1.0 24 | env: 25 | - name: LOG_LEVEL 26 | value: "INFO" 27 | - name: GGC_ROOT_PATH 28 | value: "/greengrass/v2" 29 | - name: AWS_REGION 30 | value: "" 31 | - name: PROVISION 32 | value: "true" 33 | - name: THING_NAME 34 | value: "" 35 | - name: THING_GROUP_NAME 36 | value: "" 37 | - name: TES_ROLE_NAME 38 | value: "" 39 | - name: TES_ROLE_ALIAS_NAME 40 | value: "" 41 | - name: COMPONENT_DEFAULT_USER 42 | value: "ggc_user:ggc_group" 43 | - name: DEPLOY_DEV_TOOLS 44 | value: "false" 45 | ports: 46 | - containerPort: 8883 47 | volumeMounts: 48 | - mountPath: /root/.aws 49 | name: credentials 50 | - mountPath: /greengrass/v2/logs 51 | name: logs 52 | volumes: 53 | - name: credentials 54 | hostPath: 55 | path: /home/pi/.aws 56 | type: Directory 57 | - name: logs 58 | hostPath: 59 | path: /home/pi/greengrass/v2/logs 60 | type: Directory 61 | -------------------------------------------------------------------------------- /deployment/greengrass-v2-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: greengrass 5 | --- 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | namespace: greengrass 10 | name: greengrass-deployment 11 | spec: 12 | selector: 13 | matchLabels: 14 | app: greengrass 15 | replicas: 1 16 | template: 17 | metadata: 18 | labels: 19 | app: greengrass 20 | spec: 21 | containers: 22 | - name: greengrass 23 | image: public.ecr.aws/f3r7z4u4/ggv2-arm:1.0 24 | env: 25 | - name: LOG_LEVEL 26 | value: "INFO" 27 | - name: GGC_ROOT_PATH 28 | value: "/greengrass/v2" 29 | - name: AWS_REGION 30 | value: "eu-west-1" 31 | - name: PROVISION 32 | value: "true" 33 | - name: THING_NAME 34 | value: "k3s_gg_core" 35 | - name: THING_GROUP_NAME 36 | value: "k3s_gg_core_group" 37 | - name: TES_ROLE_NAME 38 | value: "k3s_TokenExchangeRole" 39 | - name: TES_ROLE_ALIAS_NAME 40 | value: "k3s_TokenExchangeRoleAlias" 41 | - name: COMPONENT_DEFAULT_USER 42 | value: "ggc_user:ggc_group" 43 | - name: DEPLOY_DEV_TOOLS 44 | value: "false" 45 | ports: 46 | - containerPort: 8883 47 | volumeMounts: 48 | - mountPath: /root/.aws 49 | name: credentials 50 | - mountPath: /greengrass/v2/logs 51 | name: logs 52 | volumes: 53 | - name: credentials 54 | hostPath: 55 | path: /home/pi/.aws 56 | type: Directory 57 | - name: logs 58 | hostPath: 59 | path: /home/pi/greengrass/v2/logs 60 | type: Directory -------------------------------------------------------------------------------- /device/bootstrapping/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if AWS CLI is configured correctly 4 | AWS_REGION=`aws configure get region` 5 | 6 | if [ -z "$AWS_REGION" ] 7 | then 8 | echo "Region is empty, stopping installation. Please configure AWS CLI properly." 9 | exit 1 10 | else 11 | echo "Using AWS Region $AWS_REGION" 12 | fi 13 | 14 | # Update package 15 | sudo apt update -y 16 | sudo apt upgrade -y 17 | 18 | # Install Pip3, jq, and AWS CLI 19 | sudo apt install python3-pip jq docker.io -y 20 | pip3 install awscli --upgrade --user 21 | 22 | # Add user Pi to docker group 23 | 24 | sudo usermod -aG docker pi 25 | sudo systemctl enable docker 26 | 27 | # Activate kernel features 28 | 29 | KERNEL_FEATURES=' cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 swapaccount=1' 30 | 31 | if ! grep -q "cgroup_enable=memory" "/boot/cmdline.txt"; then 32 | echo "Writing $KERNEL_FEATURES into /boot/cmdline.txt" 33 | sudo sed -i '$ s/$/ cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 swapaccount=1/' /boot/cmdline.txt 34 | fi 35 | 36 | # Activate SSH 37 | sudo systemctl enable ssh 38 | sudo systemctl start ssh 39 | 40 | # Create log directory for Greengrass 41 | mkdir -p ~/greengrass/v2/logs 42 | mkdir -p /home/pi/redis/orders/data 43 | 44 | # Reading ActivateId and ActivationCode from SSM Parameter Store 45 | 46 | SSM_ACTIVATION_CODE=`aws secretsmanager get-secret-value --secret-id k3s-activation-secret --query SecretString --output text | jq --raw-output '.["activation-code"]'` 47 | SSM_ACTIVATION_ID=`aws secretsmanager get-secret-value --secret-id k3s-activation-secret --query SecretString --output text | jq --raw-output '.["activation-id"]'` 48 | AWS_REGION=`aws configure get region` 49 | 50 | # Install SSM 51 | mkdir -p /tmp/ssm 52 | sudo curl https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/debian_arm/amazon-ssm-agent.deb -o /tmp/ssm/amazon-ssm-agent.deb 53 | sudo dpkg -i /tmp/ssm/amazon-ssm-agent.deb 54 | sudo service amazon-ssm-agent stop 55 | sudo amazon-ssm-agent -register -code $SSM_ACTIVATION_CODE -id $SSM_ACTIVATION_ID -region $AWS_REGION 56 | sudo service amazon-ssm-agent start 57 | sudo reboot 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Containers @ Edge example project for k3s, Raspberry Pi, AWS IoT Greengrass, and AWS Systems Manager 2 | 3 | This example project demonstrates how to collect data from edge locations using Kubernetes (k3s) and AWS IoT Greengrass. 4 | 5 | In this documentation, we'll cover how to 6 | 7 | * Set up the infrastructure using [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) 8 | * Bootstrapping the Raspberry Pi 9 | * Deploy AWS IoT Greengrass to the edge device using kubectl 10 | 11 | The infrastructure is set up using AWS CDK and implemented with TypeScript. 12 | 13 | ## Prerequisites 14 | 15 | For this installaion, you should have the following prerequisites: 16 | 17 | * An AWS account 18 | * A properly installed and configured AWS CDK 19 | * An environment to deploy the AWS CDK application (AWS Cloud9 is highly recommended) 20 | * A Rapsberry Pi 4 with a correctly configured AWS CLI 21 | 22 | ## Packaging the application as container image 23 | 24 | The application is already packaged in a container image and can be found here. However, if you still want to build the container image yourself, it is important to run the build for ARM. This can be done directly on the Raspberry Pi, on your local workstation using `docker buildx` or in AWS using AWS CodeBuild (a blog post describing this approach can be found [here](https://aws.amazon.com/de/blogs/devops/build-arm-based-applications-using-codebuild/)). 25 | 26 | Under `docker` there is a Dockerfile called `Dockerfile-arm` which can be build using: 27 | 28 | ``` 29 | $ cd k3s-greengrass/docker 30 | $ docker buildx build --platform linux/arm/v7 -f Dockerfile-arm -t . 31 | $ docker push 32 | ``` 33 | 34 | ## Set up the infrastructure using AWS CDK 35 | 36 | After we've built and pushed the Docker image containing OpenJDK 11 and AWS IoT Greengrass, we need to set up the basic infrastructure: 37 | 38 | ``` 39 | $ npm install -g aws-cdk 40 | $ npm install 41 | $ cdk deploy // Deploys the CloudFormation template 42 | ``` 43 | 44 | ## Bootstrapping the Raspberry Pi 45 | 46 | After the CDK application has run succesfully and the necessary infrastructure is up an running, you have to set up the Raspberry Pi using bash-scripts from the Git-repository. Under `k3s-greengrass/device/bootstrapping` you can find two scripts `bootstrap.sh` and `install_k3s.sh`. 47 | 48 | ``` 49 | $ cd k3s-greengrass/device/bootstrapping 50 | $ sudo ./bootstrap.sh 51 | ``` 52 | 53 | Now you've to reboot the device to apply cgroup-changes. 54 | 55 | ``` 56 | $ cd k3s-greengrass/device/bootstrapping 57 | $ sudo ./install_k3s.sh 58 | ``` 59 | 60 | ## Deploying AWS IoT to k3s 61 | 62 | Now the k3s-cluster is up and running and you can deploy AWS IoT Greengrass to kubernetes with `k3s-greengrass/deployment/greengrass-v2-deployment.yaml` using the following commands: 63 | 64 | ``` 65 | $ export KUBECONFIG=~/.kube/kubeconfig-k3s 66 | $ kubectl apply -f k3s-greengrass/deployment/greengrass-v2-deployment.yaml 67 | ``` 68 | 69 | ## Contributing 70 | Please create a new GitHub issue for any feature requests, bugs, or documentation improvements. 71 | 72 | Where possible, please also submit a pull request for the change. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /cdk_app/k3s-bootstrap/lib/k3s-bootstrap-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import { Stack, StackProps } from 'aws-cdk-lib'; 3 | import { aws_iam as iam } from 'aws-cdk-lib'; 4 | import { aws_ssm as ssm } from 'aws-cdk-lib'; 5 | import { aws_s3 as s3 } from 'aws-cdk-lib'; 6 | import { aws_secretsmanager as secretsmanager } from 'aws-cdk-lib'; 7 | import { custom_resources as cr } from 'aws-cdk-lib'; 8 | import { Construct } from 'constructs'; 9 | 10 | import { CfnOutput } from 'aws-cdk-lib'; 11 | 12 | export class K3SBootstrapStack extends cdk.Stack { 13 | constructor(scope: Construct, id: string, props?: StackProps) { 14 | super(scope, id, props); 15 | 16 | // First create an Amazon S3 bucket 17 | 18 | const s3Bucket = new s3.Bucket(this, 'kubernetes-config', { 19 | versioned: true 20 | }); 21 | 22 | // Now we have to write the bucket name to SSM Parameter Store 23 | 24 | new ssm.StringParameter(this, 'bucketName', { 25 | parameterName: '/k3s/kubernetes/s3-bucket', 26 | stringValue: s3Bucket.bucketName 27 | }); 28 | 29 | // Second create SSM activation for Raspberry Pi 30 | 31 | const managedInstanceRole = new iam.Role(this, 'managed-instance-role', { 32 | assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com') 33 | }) 34 | 35 | managedInstanceRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')); 36 | 37 | const customResource = new cr.AwsCustomResource(this, 'activation-custom-resource', { 38 | installLatestAwsSdk: true, 39 | policy: cr.AwsCustomResourcePolicy.fromStatements( [ 40 | new iam.PolicyStatement({ 41 | effect: iam.Effect.ALLOW, 42 | actions: [ 43 | 'ssm:CreateActivation', 44 | 'ssm:DeleteActivation' 45 | ], 46 | resources: ['*'] 47 | }), 48 | new iam.PolicyStatement({ 49 | effect: iam.Effect.ALLOW, 50 | actions: ['iam:PassRole'], 51 | resources: [ managedInstanceRole.roleArn ] 52 | }) 53 | ]), 54 | onCreate: { 55 | service: 'SSM', 56 | action: 'createActivation', 57 | parameters: { 58 | IamRole: managedInstanceRole.roleName, 59 | }, 60 | physicalResourceId: cr.PhysicalResourceId.fromResponse('ActivationId') 61 | }, 62 | onDelete: { 63 | service: 'SSM', 64 | action: 'deleteActivation', 65 | parameters: { 66 | ActivationId: new cr.PhysicalResourceIdReference(), 67 | } 68 | }, 69 | }); 70 | 71 | new ssm.StringParameter(this, 'activation-id', { 72 | parameterName: '/k3s/iot/ssm-activation-id', 73 | stringValue: customResource.getResponseField('ActivationId') 74 | }); 75 | 76 | new ssm.StringParameter(this, 'activationCode', { 77 | parameterName: '/k3s/iot/ssm-activation-code', 78 | stringValue: customResource.getResponseField('ActivationCode') 79 | }); 80 | 81 | const secret = new secretsmanager.Secret(this, 'activation-secret', {secretName: 'k3s-activation-secret'}); 82 | 83 | // we need to use escape hatches to set the secret value. See https://github.com/aws/aws-cdk/issues/5810#issuecomment-672736662 84 | const cfnSecret = secret.node.defaultChild as secretsmanager.CfnSecret; 85 | 86 | 87 | cfnSecret.generateSecretString = undefined; 88 | cfnSecret.secretString = JSON.stringify( { 89 | 'activation-id': customResource.getResponseField('ActivationId'), 90 | 'activation-code' : customResource.getResponseField('ActivationCode') 91 | }); 92 | 93 | secret.grantRead(managedInstanceRole); 94 | 95 | new CfnOutput(this, 'secret-name-output', { 96 | exportName: 'activation-id-secret-name', 97 | value: secret.secretName 98 | }) 99 | 100 | } 101 | } -------------------------------------------------------------------------------- /docker/greengrass-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # Path that initial installation files are copied to 6 | INIT_JAR_PATH=/opt/greengrassv2 7 | 8 | # Allow the root user to execute commands as other users 9 | modify_sudoers() { 10 | 11 | # Grab the line number for the root user entry 12 | ROOT_LINE_NUM=$(grep -n "^root" /etc/sudoers | cut -d : -f 1) 13 | 14 | # Check if the root user is already configured to execute commands as other users 15 | if sudo sed -n "${ROOT_LINE_NUM}p" /etc/sudoers | grep -q "ALL=(ALL:ALL)" ; then 16 | echo "Root user is already configured to execute commands as other users." 17 | return 0 18 | fi 19 | 20 | echo "Attempting to safely modify /etc/sudoers..." 21 | 22 | # Take a backup of /etc/sudoers 23 | sudo cp /etc/sudoers /tmp/sudoers.bak 24 | 25 | # Replace `ALL=(ALL)` with `ALL=(ALL:ALL)` to allow the root user to execute commands as other users 26 | sudo sed -i "$ROOT_LINE_NUM s/ALL=(ALL)/ALL=(ALL:ALL)/" /tmp/sudoers.bak 27 | 28 | # Validate syntax of backup file 29 | sudo visudo -cf /tmp/sudoers.bak 30 | if [ $? -eq 0 ]; then 31 | # Replace the sudoers file with the new only if syntax is correct. 32 | sudo mv /tmp/sudoers.bak /etc/sudoers 33 | echo "Successfully modified /etc/sudoers. Root user is now configured to execute commands as other users." 34 | else 35 | echo "Error while trying to modify /etc/sudoers, please edit manually." 36 | exit 1 37 | fi 38 | } 39 | 40 | OPTIONS="-Droot=${GGC_ROOT_PATH} -Dlog.store=FILE -Dlogging.outputDirectory=/logs -Dlog.level=${LOG_LEVEL} -jar ${INIT_JAR_PATH}/lib/Greengrass.jar --provision ${PROVISION} --deploy-dev-tools ${DEPLOY_DEV_TOOLS} --start true" 41 | 42 | echo "Using the following option: $OPTIONS" 43 | 44 | # If provision is true 45 | if [ ${PROVISION} = "true" ]; then 46 | 47 | if [ ! -f "/root/.aws/credentials" ]; then 48 | echo "Provision is set to true, but credentials file does not exist at /root/.aws/credentials . Please mount to this location and retry." 49 | exit 1 50 | fi 51 | 52 | # If thing name is specified, add optional argument 53 | # If not specified, reverts to default of "GreengrassV2IotThing_" plus a random UUID. 54 | if [ ${THING_NAME} != default_thing_name ]; then 55 | OPTIONS="${OPTIONS} --thing-name ${THING_NAME}" 56 | 57 | # If thing group name is specified, add optional argument 58 | if [ ${THING_GROUP_NAME} != default_thing_group_name ]; then 59 | OPTIONS="${OPTIONS} --thing-group-name ${THING_GROUP_NAME}" 60 | fi 61 | fi 62 | fi 63 | 64 | # If TES role name is specified, add optional argument 65 | # If not specified, reverts to default of "GreengrassV2TokenExchangeRole" 66 | if [ ${TES_ROLE_NAME} != default_tes_role_name ]; then 67 | OPTIONS="${OPTIONS} --tes-role-name ${TES_ROLE_NAME}" 68 | fi 69 | 70 | # If TES role name is specified, add optional argument 71 | # If not specified, reverts to default of "GreengrassV2TokenExchangeRoleAlias" 72 | if [ ${TES_ROLE_ALIAS_NAME} != default_tes_role_alias_name ]; then 73 | OPTIONS="${OPTIONS} --tes-role-alias-name ${TES_ROLE_ALIAS_NAME}" 74 | fi 75 | 76 | # If component default user is specified, add optional argument 77 | # If not specified, reverts to ggc_user:ggc_group 78 | if [ ${COMPONENT_DEFAULT_USER} != default_component_user ]; then 79 | OPTIONS="${OPTIONS} --component-default-user ${COMPONENT_DEFAULT_USER}" 80 | fi 81 | 82 | # Use optional init config argument 83 | # If this option is specified, the config file must be mounted to this location 84 | if [ ${INIT_CONFIG} != default_init_config ]; then 85 | if [ -f ${INIT_CONFIG} ]; then 86 | echo "Using specified init config file at ${INIT_CONFIG}" 87 | OPTIONS="${OPTIONS} --init-config ${INIT_CONFIG}" 88 | else 89 | echo "WARNING: Specified init config file does not exist at ${INIT_CONFIG} !" 90 | fi 91 | fi 92 | 93 | echo "Running Greengrass with the following options: ${OPTIONS}" 94 | 95 | # If we have not already installed Greengrass 96 | if [ ! -d $GGC_ROOT_PATH/alts/current/distro ]; then 97 | # Install Greengrass via the main installer, but do not start running 98 | echo "Installing Greengrass for the first time..." 99 | modify_sudoers 100 | java ${OPTIONS} 101 | else 102 | echo "Reusing existing Greengrass installation..." 103 | fi 104 | 105 | #Make loader script executable 106 | echo "Making loader script executable..." 107 | chmod +x $GGC_ROOT_PATH/alts/current/distro/bin/loader 108 | 109 | echo "Starting Greengrass..." 110 | 111 | # Start greengrass kernel via the loader script and register container as a thing 112 | sh $GGC_ROOT_PATH/alts/current/distro/bin/loader 113 | --------------------------------------------------------------------------------