From 650dcdbf43d42d5b9e6ee155cf8b0a00dc104f7f Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Thu, 16 Feb 2023 16:28:14 -0600 Subject: [PATCH 01/37] Change k3s.yaml from 127.0.0.1 to correct IP Signed-off-by: Alexandre Peixoto Ferreira --- charts/smarter-k3s-edge/templates/common.yaml | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/charts/smarter-k3s-edge/templates/common.yaml b/charts/smarter-k3s-edge/templates/common.yaml index c748663..4d5980a 100644 --- a/charts/smarter-k3s-edge/templates/common.yaml +++ b/charts/smarter-k3s-edge/templates/common.yaml @@ -127,6 +127,23 @@ data: --node-label smarter.type=edge \\ --node-taint smarter.type=edge:NoSchedule \\ --node-label smarter-build=user-installed + nginx-start.sh: | + #!/bin/bash + # + apk update + apk add openssl + echo -e "US\n\n\nSmarter\n\n"{{ default .Values.configuration.hostIP .Values.configuration.externalHostIP }}"\n\n" | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt + openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 + mkdir -p /var/www/html + until [ -f /etc/rancher/k3s/k3s.yaml ] + do + sleep 1 + done + sed -e "s/127\.0\.0\.1/"{{ default .Values.configuration.hostIP .Values.configuration.externalHostIP }}"/" /etc/rancher/k3s/k3s.yaml > /var/www/html/k3s.yaml.{{ .Values.configuration.id }} + ln -s /var/lib/rancher/k3s/server/token /var/www/html/token.{{ .Values.configuration.id }} + ln -s /etc/nginx/conf.d/k3s-start.sh /var/www/html/k3s-start.sh.{{ .Values.configuration.id }} + chmod -R ago+rw /var/www/html + nginx -c /etc/nginx/conf.d/default.conf -g 'daemon off;' --- {{- end }} apiVersion: apps/v1 @@ -173,8 +190,7 @@ spec: - name: {{ .Values.application.appName }}-nginx image: nginx:1.23.2-alpine command: [ "/bin/sh", - "-c", - "apk update;apk add openssl;echo -e '\n\n\n\n\n\n\n' | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt;openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048;mkdir -p /var/www/html;ln -s /etc/rancher/k3s/k3s.yaml /var/www/html/k3s.yaml.{{ .Values.configuration.id }};ln -s /var/lib/rancher/k3s/server/token /var/www/html/token.{{ .Values.configuration.id }};ln -s /etc/nginx/conf.d/k3s-start.sh /var/www/html/k3s-start.sh.{{ .Values.configuration.id }};chmod -R ago+rw /var/www/html;nginx -c /etc/nginx/conf.d/default.conf -g 'daemon off;'" ] + "/etc/nginx/conf.d/nginx-start.sh"] volumeMounts: - name: k3s-data mountPath: /var/lib/rancher/k3s From a62665f54537dcdc34b9764cdff4b25cc7a3969c Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Thu, 16 Feb 2023 16:59:06 -0600 Subject: [PATCH 02/37] Fix chart version so artifacts are created Signed-off-by: Alexandre Peixoto Ferreira --- charts/smarter-k3s-edge/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/smarter-k3s-edge/Chart.yaml b/charts/smarter-k3s-edge/Chart.yaml index 4909df6..9413763 100644 --- a/charts/smarter-k3s-edge/Chart.yaml +++ b/charts/smarter-k3s-edge/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: smarter-k3s-edge -version: 0.0.3 +version: 0.0.4 type: application appVersion: v1.25.3-k3s1 description: K3s server on kubernetes From e91a93a538a299a5610aa0d3c4b711a14e5b4f51 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Thu, 23 Feb 2023 17:04:55 -0600 Subject: [PATCH 03/37] Fix k3s-start.sh script Signed-off-by: Alexandre Peixoto Ferreira --- charts/smarter-k3s-edge/Chart.yaml | 2 +- charts/smarter-k3s-edge/templates/common.yaml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/charts/smarter-k3s-edge/Chart.yaml b/charts/smarter-k3s-edge/Chart.yaml index 9413763..12d629a 100644 --- a/charts/smarter-k3s-edge/Chart.yaml +++ b/charts/smarter-k3s-edge/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: smarter-k3s-edge -version: 0.0.4 +version: 0.0.5 type: application appVersion: v1.25.3-k3s1 description: K3s server on kubernetes diff --git a/charts/smarter-k3s-edge/templates/common.yaml b/charts/smarter-k3s-edge/templates/common.yaml index 4d5980a..6bc0ee1 100644 --- a/charts/smarter-k3s-edge/templates/common.yaml +++ b/charts/smarter-k3s-edge/templates/common.yaml @@ -118,14 +118,14 @@ data: export K3S_TOKEN=$(cat token.{{ .Values.configuration.id }}) export K3S_URL=$(grep server: k3s.yaml.{{ .Values.configuration.id }} | sed -e "s/^ *.server: *//") - curl -sfL https://get.k3s.io | \\ - sh -s - \\ - --kubelet-arg cluster-dns=169.254.0.2 \\ - --log /var/log/k3s.log \\ - --node-label smarter.nodetype=unknown \\ - --node-label smarter.nodemodel=unknown \\ - --node-label smarter.type=edge \\ - --node-taint smarter.type=edge:NoSchedule \\ + curl -sfL https://get.k3s.io | \ + sh -s - \ + --kubelet-arg cluster-dns=169.254.0.2 \ + --log /var/log/k3s.log \ + --node-label smarter.nodetype=unknown \ + --node-label smarter.nodemodel=unknown \ + --node-label smarter.type=edge \ + --node-taint smarter.type=edge:NoSchedule \ --node-label smarter-build=user-installed nginx-start.sh: | #!/bin/bash From 9261c86dde660bb7e83992b4c5fd5da3a2204017 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Fri, 24 Feb 2023 13:59:39 -0600 Subject: [PATCH 04/37] Allow grafana host to be renamed Signed-off-by: Alexandre Peixoto Ferreira --- charts/smarter-cloud/Chart.yaml | 2 +- charts/smarter-cloud/templates/NOTES.txt | 2 +- charts/smarter-cloud/templates/grafana-ingress.yaml | 4 ++-- charts/smarter-cloud/values.yaml | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/charts/smarter-cloud/Chart.yaml b/charts/smarter-cloud/Chart.yaml index dcad7a1..7426b35 100644 --- a/charts/smarter-cloud/Chart.yaml +++ b/charts/smarter-cloud/Chart.yaml @@ -17,7 +17,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.3 +version: 0.1.4 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/smarter-cloud/templates/NOTES.txt b/charts/smarter-cloud/templates/NOTES.txt index 5eb9e49..b9ed3db 100644 --- a/charts/smarter-cloud/templates/NOTES.txt +++ b/charts/smarter-cloud/templates/NOTES.txt @@ -2,5 +2,5 @@ Your SMARTER cloud instance has been deployed. Next deploy an edge server and e For more details and instructions go to https://getsmarter.io If you deployed your instance correctly, you should be able to log into grafana at -https://grafana.{{ .Values.domain }} with the username admin and the password {{ .Values.prometheus.grafana.adminPassword }} +https://{{ .Values.host }}.{{ .Values.domain }} with the username admin and the password {{ .Values.prometheus.grafana.adminPassword }} diff --git a/charts/smarter-cloud/templates/grafana-ingress.yaml b/charts/smarter-cloud/templates/grafana-ingress.yaml index 775bb9f..c92a412 100644 --- a/charts/smarter-cloud/templates/grafana-ingress.yaml +++ b/charts/smarter-cloud/templates/grafana-ingress.yaml @@ -7,7 +7,7 @@ metadata: cert-manager.io/cluster-issuer: cert-manager-acme-issuer spec: rules: - - host: grafana.{{ .Values.domain }} + - host: {{ .Values.host }}.{{ .Values.domain }} http: paths: - path: / @@ -20,4 +20,4 @@ spec: tls: - secretName: {{ .Release.Name }}-grafana-tls hosts: - - grafana.{{ .Values.domain }} \ No newline at end of file + - {{ .Values.host }}.{{ .Values.domain }} diff --git a/charts/smarter-cloud/values.yaml b/charts/smarter-cloud/values.yaml index ef4a01d..a4eb93f 100644 --- a/charts/smarter-cloud/values.yaml +++ b/charts/smarter-cloud/values.yaml @@ -2,6 +2,7 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. +host: grafana domain: example.com cert-manager: From eec371ae97f628d9763b8d89a71abd4c250bd6dd Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Fri, 24 Feb 2023 16:09:11 -0600 Subject: [PATCH 05/37] Add support for labeling node automatically Signed-off-by: Alexandre Peixoto Ferreira --- charts/smarter-k3s-edge/Chart.yaml | 2 +- charts/smarter-k3s-edge/templates/common.yaml | 8 ++++++++ charts/smarter-k3s-edge/values.yaml | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/charts/smarter-k3s-edge/Chart.yaml b/charts/smarter-k3s-edge/Chart.yaml index 12d629a..db2d068 100644 --- a/charts/smarter-k3s-edge/Chart.yaml +++ b/charts/smarter-k3s-edge/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: smarter-k3s-edge -version: 0.0.5 +version: 0.0.6 type: application appVersion: v1.25.3-k3s1 description: K3s server on kubernetes diff --git a/charts/smarter-k3s-edge/templates/common.yaml b/charts/smarter-k3s-edge/templates/common.yaml index 6bc0ee1..4830279 100644 --- a/charts/smarter-k3s-edge/templates/common.yaml +++ b/charts/smarter-k3s-edge/templates/common.yaml @@ -126,6 +126,14 @@ data: --node-label smarter.nodemodel=unknown \ --node-label smarter.type=edge \ --node-taint smarter.type=edge:NoSchedule \ + {{- if .Values.configuration.smarter_demo_labels }} + --node-label smarter-audio-client=enabled \ + --node-label smarter-gstreamer=enabled \ + --node-label smarter-image-detector=enabled \ + --node-label smarter-pulseaudio=enabled \ + --node-label smarter-inference=enabled \ + --node-label smarter-fluent-bit=enabled \ + {{- end }} --node-label smarter-build=user-installed nginx-start.sh: | #!/bin/bash diff --git a/charts/smarter-k3s-edge/values.yaml b/charts/smarter-k3s-edge/values.yaml index 916fb6a..508ee9c 100644 --- a/charts/smarter-k3s-edge/values.yaml +++ b/charts/smarter-k3s-edge/values.yaml @@ -16,4 +16,5 @@ configuration: port: 6443 # Comment to remove NGINX portHTTPS: 6453 - # set id to paqssword + # Uncomment to enable labeling for smarter-demo + #smarter_demo_labels: true From aa7ed8532ad6eb57cb16bf85adc6db376b0cd9f9 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Fri, 24 Feb 2023 18:13:24 -0600 Subject: [PATCH 06/37] Adding terraform scripts Signed-off-by: Alexandre Peixoto Ferreira --- .../example/single-node/set-label-smarter.sh | 3 + terraform/example/single-node/smarter-main.tf | 122 +++++++++++++++++ terraform/main.tf | 128 ++++++++++++++++++ terraform/variables.tf | 94 +++++++++++++ terraform/worker_nodes.tf | 38 ++++++ 5 files changed, 385 insertions(+) create mode 100644 terraform/example/single-node/set-label-smarter.sh create mode 100644 terraform/example/single-node/smarter-main.tf create mode 100644 terraform/main.tf create mode 100644 terraform/variables.tf create mode 100644 terraform/worker_nodes.tf diff --git a/terraform/example/single-node/set-label-smarter.sh b/terraform/example/single-node/set-label-smarter.sh new file mode 100644 index 0000000..0511996 --- /dev/null +++ b/terraform/example/single-node/set-label-smarter.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# +kubectl label node raspberrypi4 $(kubectl get ds -n smarter | cut -c 96-126 | grep smarter-) diff --git a/terraform/example/single-node/smarter-main.tf b/terraform/example/single-node/smarter-main.tf new file mode 100644 index 0000000..fa6f30f --- /dev/null +++ b/terraform/example/single-node/smarter-main.tf @@ -0,0 +1,122 @@ +provider "aws" { + region = "eu-west-1" +} + +locals { + deployment_name = "smarter-alex" +} + +data "aws_vpc" "vpc" { + # This assumes that there is a default VPC + default = true +} + +resource "aws_security_group" "sg" { + name = "allow_smarter" + vpc_id = data.aws_vpc.vpc.id + + ingress { + description = "allow_ssh" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + from_port = 22 + to_port = 22 + protocol = "tcp" + } + + ingress { + description = "allow_https" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + from_port = 443 + to_port = 443 + protocol = "tcp" + } + + ingress { + description = "allow_k3s_inbound" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + from_port = 6443 + to_port = 6443 + protocol = "tcp" + } + + ingress { + description = "allow_k3s_edge_inbound" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + from_port = 6444 + to_port = 6444 + protocol = "tcp" + } + + ingress { + description = "allow_k3s_https_inbound" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + from_port = 6446 + to_port = 6446 + protocol = "tcp" + } + + ingress { + description = "allow_fluentbit_inbound" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + from_port = 30224 + to_port = 30224 + protocol = "tcp" + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + + tags = { + Name = "allow_smarter" + } +} + +module "ssh_key_pair" { + # tflint-ignore: terraform_module_pinned_source + source = "git::https://github.com/cloudposse/terraform-aws-key-pair.git?ref=master" + namespace = local.deployment_name + stage = "prod" + name = "k3s" + ssh_public_key_path = "ssh" + generate_ssh_key = "true" + private_key_extension = ".pem" + public_key_extension = ".pub" +} + +module "k3s" { + source = "../../" + + providers = { + aws = aws + } + + assign_public_ip = true + deployment_name = "smarter-alex" + instance_type = "t3a.medium" + #ami_id = ami-0333305f9719618c7 (ubuntu 22.04 20230115) + subnet_id = "subnet-0a0e6c54239cf12fc" + keypair_content = module.ssh_key_pair.public_key + security_group_ids = [aws_security_group.sg.id] + kubeconfig_mode = "644" + letsencrypt_email = "alexandre.ferreira@arm.com" + +} + +output "k3s_master_public_dns" { + value = module.k3s.instance.public_dns +} + +output "k3s_edge" { + value = module.k3s.k3s_edge.result +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..3ece916 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,128 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.16" + } + } + + required_version = ">= 1.2.0" +} + +data "aws_ami" "ubuntu" { + most_recent = true + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] + } + owners = ["099720109477"] +} + +resource "random_string" "agent_token" { + length = 24 + special = false +} + +resource "random_string" "k3s_edge_id" { + length = 24 + special = false +} + +resource "aws_iam_instance_profile" "instance_profile" { + name = "${var.deployment_name}-InstanceProfile" + role = var.iam_role_name + count = var.iam_role_name == null ? 0 : 1 +} + +data "cloudinit_config" "userData" { + part { + content = < /tmp/variables +curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE=${var.kubeconfig_mode} K3S_TOKEN=${random_string.agent_token.result} sh - +echo "----- updating ubuntu" +apt-get update -y && apt-get upgrade -y && apt-get install awscli git -y +echo "----- Adding helm" +curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash +echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> /home/ubuntu/.profile +echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> /home/ubuntu/.bashrc +export ADVERTISE_IP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4) +export LOCAL_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4) +echo "----- Wating for k3s to start" +until [ -f /etc/rancher/k3s/k3s.yaml ] +do + sleep 5 +done +echo "----- Adding smarter-cloud to k3s" +sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d "." -f 1) --set domain=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d "." -f 2-) --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" +echo "----- Adding smarter-edge to k3s" +sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTPS=6446 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --wait" +echo "----- Waiting for k3s.yaml from k3s-edge" +until [ -f /home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result} ] +do + sudo su - ubuntu bash -c "wget --no-check-certificate https://$ADVERTISE_IP:6446/k3s.yaml.${random_string.k3s_edge_id.result}" + sleep 5 +done +echo "----- Adding smarter-edge to k3s-edge" +sudo su - ubuntu bash -c "export KUBECONFIG=/home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result};helm install --create-namespace --namespace smarter my-smartercloud-edge smarter/smarter-edge --wait;helm install --create-namespace --namespace smarter --set global.domain=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d "." -f 2-) --set smarter-fluent-bit.fluentd.host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d "." -f 1) my-smartercloud-demo smarter/smarter-demo --wait" +echo "----- Finished installing" +EOF + content_type = "text/x-shellscript" + } + + part { + content = var.manifest_bucket_path == "" ? "" : < Date: Mon, 27 Feb 2023 12:27:36 -0600 Subject: [PATCH 07/37] Update of terraform for running smarter on EC2 Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 37 +++++++++++++++++++ terraform/example/single-node/smarter-main.tf | 18 +++++++-- terraform/main.tf | 2 +- 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 terraform/README.md diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..1ec1d92 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,37 @@ +# Terrform script to install smarter on AWS EC2 + +It assumes that the enviroment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN are set correctly so terraform can access AWS. +Set the following variables to correct values: +region (provider "aws): AWS region to allocate an EC2 instance on. +deployment-name (locals): terraform name for this deployment, also used for helm + +## Running + +Move to the directory example/single-node and update the smarter-main.tf variables: deployment-name and letsencrypt_email to be valid +Run the following command from the directory example/single-node: +``` +terraform apply +``` + +## Outputs + +Terraform will output the name of EC2 instance allocated and password/ID generated by terraform. + +Grafana web interface instance on EC2 (k3s cloud) can be accessed by https://grafana-.nio.io with user admin and password . + +A ssh directory will be created locally containing a private/public SSH key that can be used to access the instance using the following command: + +```bash +ssh -i ssh/-prod-k3s.pem ubuntu@ +``` + +on the instance access to k3s cloud (running the cloud containers) can be accessed by setting KUBECONFIG to /etc/rancher/k3s/k3s.yaml +The k3s edge, that manages the edge devices and applications running on them, can be accessd by setting KUBECONFIG as $(pwd)/k3s.yaml.< + +Helm was used to install charts and can be used to manage them by setting the correct KUBECONFIG + +The edge devices can be installed (Raspberry pi4 for example) by running the following script. The command will install k3s agent and connect that to the k3s edge running on the ec2 instanct. The command will install k3s agent and connect that to the k3s edge running on the ec2 instance. +``` +wget --no-check-certificate https://:6446/k3s-start.sh. | bash -s - +``` + diff --git a/terraform/example/single-node/smarter-main.tf b/terraform/example/single-node/smarter-main.tf index fa6f30f..97c01ac 100644 --- a/terraform/example/single-node/smarter-main.tf +++ b/terraform/example/single-node/smarter-main.tf @@ -3,7 +3,7 @@ provider "aws" { } locals { - deployment_name = "smarter-alex" + deployment_name = "smarter-testing" } data "aws_vpc" "vpc" { @@ -24,6 +24,15 @@ resource "aws_security_group" "sg" { protocol = "tcp" } + ingress { + description = "allow_http" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + from_port = 80 + to_port = 80 + protocol = "tcp" + } + ingress { description = "allow_https" cidr_blocks = ["0.0.0.0/0"] @@ -102,21 +111,22 @@ module "k3s" { } assign_public_ip = true - deployment_name = "smarter-alex" + deployment_name = "smarter-testing" instance_type = "t3a.medium" #ami_id = ami-0333305f9719618c7 (ubuntu 22.04 20230115) subnet_id = "subnet-0a0e6c54239cf12fc" keypair_content = module.ssh_key_pair.public_key security_group_ids = [aws_security_group.sg.id] kubeconfig_mode = "644" - letsencrypt_email = "alexandre.ferreira@arm.com" - + letsencrypt_email = "xxx@yyy.com" } output "k3s_master_public_dns" { value = module.k3s.instance.public_dns + description = "EC2 instance name allocated" } output "k3s_edge" { value = module.k3s.k3s_edge.result + description = "System-wide password: grafana admin, k3s-edge ID" } diff --git a/terraform/main.tf b/terraform/main.tf index 3ece916..f3aaafb 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -65,7 +65,7 @@ do sleep 5 done echo "----- Adding smarter-cloud to k3s" -sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d "." -f 1) --set domain=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d "." -f 2-) --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" +sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d "." -f 1 | sed -e "s/^ec2/grafana/") --set domain=nip.io --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" echo "----- Adding smarter-edge to k3s" sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTPS=6446 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --wait" echo "----- Waiting for k3s.yaml from k3s-edge" From 76a822768060385014e1910f940f539f53e0bcd9 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Mon, 27 Feb 2023 12:44:05 -0600 Subject: [PATCH 08/37] Fixing documentation and some leftover varibles Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 1 + terraform/example/single-node/.gitignore | 4 ++ terraform/example/single-node/smarter-main.tf | 2 +- terraform/worker_nodes.tf | 38 ------------------- 4 files changed, 6 insertions(+), 39 deletions(-) create mode 100644 terraform/example/single-node/.gitignore delete mode 100644 terraform/worker_nodes.tf diff --git a/terraform/README.md b/terraform/README.md index 1ec1d92..b7f70ec 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -10,6 +10,7 @@ deployment-name (locals): terraform name for this deployment, also used for helm Move to the directory example/single-node and update the smarter-main.tf variables: deployment-name and letsencrypt_email to be valid Run the following command from the directory example/single-node: ``` +terraform init terraform apply ``` diff --git a/terraform/example/single-node/.gitignore b/terraform/example/single-node/.gitignore new file mode 100644 index 0000000..d9ba4ec --- /dev/null +++ b/terraform/example/single-node/.gitignore @@ -0,0 +1,4 @@ +.terraform.lock.hcl +.terraform +ssh +terraform.tfstate diff --git a/terraform/example/single-node/smarter-main.tf b/terraform/example/single-node/smarter-main.tf index 97c01ac..e318a2a 100644 --- a/terraform/example/single-node/smarter-main.tf +++ b/terraform/example/single-node/smarter-main.tf @@ -114,7 +114,7 @@ module "k3s" { deployment_name = "smarter-testing" instance_type = "t3a.medium" #ami_id = ami-0333305f9719618c7 (ubuntu 22.04 20230115) - subnet_id = "subnet-0a0e6c54239cf12fc" + #subnet_id = "subnet-xxxxx" #If there is no default subnet keypair_content = module.ssh_key_pair.public_key security_group_ids = [aws_security_group.sg.id] kubeconfig_mode = "644" diff --git a/terraform/worker_nodes.tf b/terraform/worker_nodes.tf deleted file mode 100644 index 3dd8820..0000000 --- a/terraform/worker_nodes.tf +++ /dev/null @@ -1,38 +0,0 @@ -data "cloudinit_config" "agent_user_data" { - part { - content = < Date: Tue, 28 Feb 2023 08:45:41 -0600 Subject: [PATCH 09/37] Add support for traefik at nginx allowing use of letsencrypt Signed-off-by: Alexandre Peixoto Ferreira --- charts/smarter-k3s-edge/Chart.yaml | 2 +- charts/smarter-k3s-edge/templates/common.yaml | 35 +++++++++++++------ .../templates/k3s-edge-ingress.yaml | 25 +++++++++++++ charts/smarter-k3s-edge/values.yaml | 9 +++-- 4 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml diff --git a/charts/smarter-k3s-edge/Chart.yaml b/charts/smarter-k3s-edge/Chart.yaml index db2d068..81578d1 100644 --- a/charts/smarter-k3s-edge/Chart.yaml +++ b/charts/smarter-k3s-edge/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: smarter-k3s-edge -version: 0.0.6 +version: 0.0.7 type: application appVersion: v1.25.3-k3s1 description: K3s server on kubernetes diff --git a/charts/smarter-k3s-edge/templates/common.yaml b/charts/smarter-k3s-edge/templates/common.yaml index 4830279..0ea1434 100644 --- a/charts/smarter-k3s-edge/templates/common.yaml +++ b/charts/smarter-k3s-edge/templates/common.yaml @@ -1,4 +1,4 @@ -{{- if .Values.configuration.portHTTPS }} +{{- if or .Values.configuration.portHTTP .Values.configuration.portHTTPS }} apiVersion: v1 kind: PersistentVolumeClaim metadata: @@ -46,14 +46,14 @@ data: #gzip on; - server { - listen 80 default_server; - listen [::]:80 default_server; - server_name server_domain_or_IP; - return 302 https://$server_name$request_uri; - } server { disable_symlinks off; + {{- if .Values.configuration.portHTTP }} + listen {{ .Values.configuration.portHTTP }} default_server; + listen [::]:{{ .Values.configuration.portHTTP }} default_server; + server_name server_domain_or_IP; + {{- end }} + {{- if .Values.configuration.portHTTPS }} i # SSL configuration listen {{ .Values.configuration.portHTTPS }} ssl http2 default_server; listen [::]:{{ .Values.configuration.portHTTPS }} ssl http2 default_server; @@ -71,13 +71,14 @@ data: ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; + ssl_dhparam /etc/ssl/certs/dhparam.pem; + {{- end }} # Disable preloading HSTS for now. You can use the commented out header line that includes # the "preload" directive if you understand the implications. #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; - ssl_dhparam /etc/ssl/certs/dhparam.pem; root /var/www/html; server_name _; location / { @@ -112,8 +113,13 @@ data: k3s-start.sh: | #!/bin/bash # + {{- if .Values.configuration.traefik }} + curl -sflkO https://{{ .Values.configuration.host }}.{{ .Values.configuration.domain }}/token.{{ .Values.configuration.id }} + curl -sflkO https://{{ .Values.configuration.host }}.{{ .Values.configuration.domain }}/k3s.yaml.{{ .Values.configuration.id }} + {{- else }} curl -sflkO https://{{ default .Values.configuration.hostIP .Values.configuration.externalHostIP}}:{{ .Values.configuration.portHTTPS }}/token.{{ .Values.configuration.id }} curl -sflkO https://{{ default .Values.configuration.hostIP .Values.configuration.externalHostIP}}:{{ .Values.configuration.portHTTPS }}/k3s.yaml.{{ .Values.configuration.id }} + {{- end }} export INSTALL_K3S_VERSION=$(echo "{{ default .Chart.AppVersion .Values.image.tag }}" | sed -e "s/-k3/+k3/") export K3S_TOKEN=$(cat token.{{ .Values.configuration.id }}) export K3S_URL=$(grep server: k3s.yaml.{{ .Values.configuration.id }} | sed -e "s/^ *.server: *//") @@ -140,8 +146,10 @@ data: # apk update apk add openssl + {{- if .Values.configuration.portHTTPS }} i echo -e "US\n\n\nSmarter\n\n"{{ default .Values.configuration.hostIP .Values.configuration.externalHostIP }}"\n\n" | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 + {{- end }} mkdir -p /var/www/html until [ -f /etc/rancher/k3s/k3s.yaml ] do @@ -185,7 +193,7 @@ spec: "--disable","coredns", "--disable","local-storage", "--flannel-backend=none" ] - {{- if .Values.configuration.portHTTPS }} + {{- if or .Values.configuration.portHTTP .Values.configuration.portHTTPS }} volumeMounts: - name: k3s-data mountPath: /var/lib/rancher/k3s @@ -194,7 +202,7 @@ spec: {{- end }} ports: - containerPort: {{ .Values.configuration.port }} - {{- if .Values.configuration.portHTTPS }} + {{- if or .Values.configuration.portHTTP .Values.configuration.portHTTPS }} - name: {{ .Values.application.appName }}-nginx image: nginx:1.23.2-alpine command: [ "/bin/sh", @@ -207,10 +215,15 @@ spec: - name: config mountPath: /etc/nginx/conf.d ports: + {{- if .Values.configuration.portHTTP }} + - containerPort: {{ .Values.configuration.portHTTP }} + {{- end }} + {{- if .Values.configuration.portHTTPS }} - containerPort: {{ .Values.configuration.portHTTPS }} + {{- end }} {{- end }} volumes: - {{- if .Values.configuration.portHTTPS }} + {{- if or .Values.configuration.portHTTP .Values.configuration.portHTTPS }} - name: k3s-data persistentVolumeClaim: claimName: {{ .Values.application.appName }}-data diff --git a/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml b/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml new file mode 100644 index 0000000..6847ccd --- /dev/null +++ b/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml @@ -0,0 +1,25 @@ +{{- if .Values.configuration.traefik }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-k3s-edge-ingress + annotations: + kubernetes.io/ingress.class: traefik + cert-manager.io/cluster-issuer: cert-manager-acme-issuer +spec: + rules: + - host: {{ .Values.configuration.host }}.{{ .Values.configuration.domain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-k3s-edge + port: + number: {{ .Values.configuration.portHTTP }} + tls: + - secretName: {{ .Release.Name }}-k3s-edge-tls + hosts: + - {{ .Values.configuration.host }}.{{ .Values.configuration.domain }} +{{- end }} diff --git a/charts/smarter-k3s-edge/values.yaml b/charts/smarter-k3s-edge/values.yaml index 508ee9c..66b7f67 100644 --- a/charts/smarter-k3s-edge/values.yaml +++ b/charts/smarter-k3s-edge/values.yaml @@ -10,11 +10,16 @@ image: pullPolicy: IfNotPresent configuration: + host: k3s-edge + domain: example.com hostIP: 192.168.2.222 # Use this in case of NATed AWS - #externalHostIP: 192.168.2.222 + externalHostIP: 192.168.2.222 port: 6443 # Comment to remove NGINX - portHTTPS: 6453 + portHTTP: 80 + #portHTTPS: 6453 + # Uncomment to enable traefik ingress + traefik: True # Uncomment to enable labeling for smarter-demo #smarter_demo_labels: true From c163da36220fbb8cbffaf67fc7063903fcb3b0c8 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 28 Feb 2023 10:21:46 -0600 Subject: [PATCH 10/37] Fixes for uysing traefik for nginx k3s configuration Signed-off-by: Alexandre Peixoto Ferreira --- charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml | 2 +- charts/smarter-k3s-edge/templates/service.yaml | 5 +++++ charts/smarter-k3s-edge/values.yaml | 2 +- terraform/main.tf | 8 ++++---- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml b/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml index 6847ccd..f278954 100644 --- a/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml +++ b/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml @@ -15,7 +15,7 @@ spec: pathType: Prefix backend: service: - name: {{ .Release.Name }}-k3s-edge + name: {{ .Values.application.appName }} port: number: {{ .Values.configuration.portHTTP }} tls: diff --git a/charts/smarter-k3s-edge/templates/service.yaml b/charts/smarter-k3s-edge/templates/service.yaml index 8094fbe..9504afe 100644 --- a/charts/smarter-k3s-edge/templates/service.yaml +++ b/charts/smarter-k3s-edge/templates/service.yaml @@ -11,6 +11,11 @@ spec: - protocol: TCP port: {{ .Values.configuration.port }} name: {{ .Values.application.appName }} + {{- if .Values.configuration.portHTTP }} + - protocol: TCP + port: {{ .Values.configuration.portHTTP }} + name: {{ .Values.application.appName }}-http + {{- end }} {{- if .Values.configuration.portHTTPS }} - protocol: TCP port: {{ .Values.configuration.portHTTPS }} diff --git a/charts/smarter-k3s-edge/values.yaml b/charts/smarter-k3s-edge/values.yaml index 66b7f67..497124e 100644 --- a/charts/smarter-k3s-edge/values.yaml +++ b/charts/smarter-k3s-edge/values.yaml @@ -20,6 +20,6 @@ configuration: portHTTP: 80 #portHTTPS: 6453 # Uncomment to enable traefik ingress - traefik: True + #traefik: True # Uncomment to enable labeling for smarter-demo #smarter_demo_labels: true diff --git a/terraform/main.tf b/terraform/main.tf index f3aaafb..c76d512 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -65,17 +65,17 @@ do sleep 5 done echo "----- Adding smarter-cloud to k3s" -sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d "." -f 1 | sed -e "s/^ec2/grafana/") --set domain=nip.io --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" +sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d '.' -f 1 | sed -e 's/^ec2/grafana/') --set domain=nip.io --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" echo "----- Adding smarter-edge to k3s" -sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTPS=6446 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --wait" +sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTP=80 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --set configuration.host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d '.' -f 1 | sed -e 's/^ec2/k3s/') --set configuration.domain=nip.io --set configuration.traefik=true --wait" echo "----- Waiting for k3s.yaml from k3s-edge" until [ -f /home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result} ] do - sudo su - ubuntu bash -c "wget --no-check-certificate https://$ADVERTISE_IP:6446/k3s.yaml.${random_string.k3s_edge_id.result}" + sudo su - ubuntu bash -c "wget http://$ADVERTISE_IP:80/k3s.yaml.${random_string.k3s_edge_id.result}" sleep 5 done echo "----- Adding smarter-edge to k3s-edge" -sudo su - ubuntu bash -c "export KUBECONFIG=/home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result};helm install --create-namespace --namespace smarter my-smartercloud-edge smarter/smarter-edge --wait;helm install --create-namespace --namespace smarter --set global.domain=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d "." -f 2-) --set smarter-fluent-bit.fluentd.host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d "." -f 1) my-smartercloud-demo smarter/smarter-demo --wait" +sudo su - ubuntu bash -c "export KUBECONFIG=/home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result};helm install --create-namespace --namespace smarter my-smartercloud-edge smarter/smarter-edge --wait;helm install --create-namespace --namespace smarter --set global.domain=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d '.' -f 2-) --set smarter-fluent-bit.fluentd.host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d '.' -f 1) my-smartercloud-demo smarter/smarter-demo --wait" echo "----- Finished installing" EOF content_type = "text/x-shellscript" From c29dab34caad16e36e47148a80c963b2906b0c07 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 28 Feb 2023 10:27:06 -0600 Subject: [PATCH 11/37] Fix documentation to access nip.io for k3s-start.sh script Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/README.md b/terraform/README.md index b7f70ec..661aab0 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -33,6 +33,6 @@ Helm was used to install charts and can be used to manage them by setting the co The edge devices can be installed (Raspberry pi4 for example) by running the following script. The command will install k3s agent and connect that to the k3s edge running on the ec2 instanct. The command will install k3s agent and connect that to the k3s edge running on the ec2 instance. ``` -wget --no-check-certificate https://:6446/k3s-start.sh. | bash -s - +wget https://k3s-<.nio.io/k3s-start.sh. | bash -s - ``` From 812e90c12c01e8888b8b4f3fcdfc8af88ec66aa6 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 28 Feb 2023 13:20:47 -0600 Subject: [PATCH 12/37] Fix setting deployment-name Signed-off-by: Alexandre Peixoto Ferreira --- terraform/example/single-node/smarter-main.tf | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/terraform/example/single-node/smarter-main.tf b/terraform/example/single-node/smarter-main.tf index e318a2a..08816c1 100644 --- a/terraform/example/single-node/smarter-main.tf +++ b/terraform/example/single-node/smarter-main.tf @@ -12,7 +12,7 @@ data "aws_vpc" "vpc" { } resource "aws_security_group" "sg" { - name = "allow_smarter" + name = "allow-${local.deployment_name}" vpc_id = data.aws_vpc.vpc.id ingress { @@ -60,15 +60,6 @@ resource "aws_security_group" "sg" { protocol = "tcp" } - ingress { - description = "allow_k3s_https_inbound" - cidr_blocks = ["0.0.0.0/0"] - ipv6_cidr_blocks = ["::/0"] - from_port = 6446 - to_port = 6446 - protocol = "tcp" - } - ingress { description = "allow_fluentbit_inbound" cidr_blocks = ["0.0.0.0/0"] @@ -87,7 +78,7 @@ resource "aws_security_group" "sg" { } tags = { - Name = "allow_smarter" + Name = "allow-${local.deployment_name}" } } @@ -111,7 +102,7 @@ module "k3s" { } assign_public_ip = true - deployment_name = "smarter-testing" + deployment_name = local.deployment_name instance_type = "t3a.medium" #ami_id = ami-0333305f9719618c7 (ubuntu 22.04 20230115) #subnet_id = "subnet-xxxxx" #If there is no default subnet From dbf1bdc7533854989114bbfc3f0cecea292551cf Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 28 Feb 2023 13:57:07 -0600 Subject: [PATCH 13/37] Organizing files on directory to simplify user interface Signed-off-by: Alexandre Peixoto Ferreira --- terraform/{example/single-node => }/.gitignore | 0 terraform/README.md | 5 +++-- terraform/example/single-node/set-label-smarter.sh | 3 --- terraform/{ => k3s}/main.tf | 0 terraform/{ => k3s}/variables.tf | 0 terraform/{example/single-node => }/smarter-main.tf | 2 +- 6 files changed, 4 insertions(+), 6 deletions(-) rename terraform/{example/single-node => }/.gitignore (100%) delete mode 100644 terraform/example/single-node/set-label-smarter.sh rename terraform/{ => k3s}/main.tf (100%) rename terraform/{ => k3s}/variables.tf (100%) rename terraform/{example/single-node => }/smarter-main.tf (99%) diff --git a/terraform/example/single-node/.gitignore b/terraform/.gitignore similarity index 100% rename from terraform/example/single-node/.gitignore rename to terraform/.gitignore diff --git a/terraform/README.md b/terraform/README.md index 661aab0..81471f7 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -7,10 +7,11 @@ deployment-name (locals): terraform name for this deployment, also used for helm ## Running -Move to the directory example/single-node and update the smarter-main.tf variables: deployment-name and letsencrypt_email to be valid -Run the following command from the directory example/single-node: +Update the smarter-main.tf variables: deployment-name and letsencrypt_email to be valid +Run the following commands ``` terraform init +# optional: terraform plan terraform apply ``` diff --git a/terraform/example/single-node/set-label-smarter.sh b/terraform/example/single-node/set-label-smarter.sh deleted file mode 100644 index 0511996..0000000 --- a/terraform/example/single-node/set-label-smarter.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# -kubectl label node raspberrypi4 $(kubectl get ds -n smarter | cut -c 96-126 | grep smarter-) diff --git a/terraform/main.tf b/terraform/k3s/main.tf similarity index 100% rename from terraform/main.tf rename to terraform/k3s/main.tf diff --git a/terraform/variables.tf b/terraform/k3s/variables.tf similarity index 100% rename from terraform/variables.tf rename to terraform/k3s/variables.tf diff --git a/terraform/example/single-node/smarter-main.tf b/terraform/smarter-main.tf similarity index 99% rename from terraform/example/single-node/smarter-main.tf rename to terraform/smarter-main.tf index 08816c1..9d41436 100644 --- a/terraform/example/single-node/smarter-main.tf +++ b/terraform/smarter-main.tf @@ -95,7 +95,7 @@ module "ssh_key_pair" { } module "k3s" { - source = "../../" + source = "./k3s" providers = { aws = aws From 999c894baff093f2977c1887b02f105fa9745183 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 28 Feb 2023 15:17:03 -0600 Subject: [PATCH 14/37] Put letsencrypt email a snot define so terraform ask for it Signed-off-by: Alexandre Peixoto Ferreira --- terraform/smarter-main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/smarter-main.tf b/terraform/smarter-main.tf index 9d41436..e7a01d0 100644 --- a/terraform/smarter-main.tf +++ b/terraform/smarter-main.tf @@ -109,7 +109,7 @@ module "k3s" { keypair_content = module.ssh_key_pair.public_key security_group_ids = [aws_security_group.sg.id] kubeconfig_mode = "644" - letsencrypt_email = "xxx@yyy.com" + #letsencrypt_email = "xxx@yyy.com" } output "k3s_master_public_dns" { From 59b37e139b304ba32d9d576f17d1853fcbd0a54c Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Thu, 23 Mar 2023 13:48:03 -0500 Subject: [PATCH 15/37] Add troubleshooting section Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/terraform/README.md b/terraform/README.md index 81471f7..790ee71 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -37,3 +37,13 @@ The edge devices can be installed (Raspberry pi4 for example) by running the fol wget https://k3s-<.nio.io/k3s-start.sh. | bash -s - ``` +# Troubleshooting + +## AWS authentication + +Use the AWS credentials provided in the "Get credentials for ProjAdmins" page. +Terraform expects the following environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN. + +## Networking + +If an error appear about "default subnet not found", a subnet was not defined ias default for the VPC. A subnet can be set in modules "k3s" object. Uncomment the subnet_id and use one valid for your VPC. From 6bf1ce0828e1402609a900d1fda1ef3c14acf94d Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Thu, 23 Mar 2023 16:56:26 -0500 Subject: [PATCH 16/37] Fixes for traefik Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 4 ++-- terraform/k3s/main.tf | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index 790ee71..d0cf15d 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -19,7 +19,7 @@ terraform apply Terraform will output the name of EC2 instance allocated and password/ID generated by terraform. -Grafana web interface instance on EC2 (k3s cloud) can be accessed by https://grafana-.nio.io with user admin and password . +Grafana web interface instance on EC2 (k3s cloud) can be accessed by https://grafana-.nip.io with user admin and password . A ssh directory will be created locally containing a private/public SSH key that can be used to access the instance using the following command: @@ -34,7 +34,7 @@ Helm was used to install charts and can be used to manage them by setting the co The edge devices can be installed (Raspberry pi4 for example) by running the following script. The command will install k3s agent and connect that to the k3s edge running on the ec2 instanct. The command will install k3s agent and connect that to the k3s edge running on the ec2 instance. ``` -wget https://k3s-<.nio.io/k3s-start.sh. | bash -s - +wget https://k3s-<.nip.io/k3s-start.sh. | bash -s - ``` # Troubleshooting diff --git a/terraform/k3s/main.tf b/terraform/k3s/main.tf index c76d512..6ac3b2e 100644 --- a/terraform/k3s/main.tf +++ b/terraform/k3s/main.tf @@ -58,6 +58,7 @@ curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> /home/ubuntu/.profile echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> /home/ubuntu/.bashrc export ADVERTISE_IP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4) +export PUBLIC_HOSTNAME=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d '.' -f 1 | sed 's/^ec2-//') export LOCAL_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4) echo "----- Wating for k3s to start" until [ -f /etc/rancher/k3s/k3s.yaml ] @@ -65,13 +66,13 @@ do sleep 5 done echo "----- Adding smarter-cloud to k3s" -sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d '.' -f 1 | sed -e 's/^ec2/grafana/') --set domain=nip.io --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" +sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=grafana-$PUBLIC_HOSTNAME --set domain=nip.io --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" echo "----- Adding smarter-edge to k3s" -sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTP=80 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --set configuration.host=$(curl http://169.254.169.254/latest/meta-data/public-hostname | cut -d '.' -f 1 | sed -e 's/^ec2/k3s/') --set configuration.domain=nip.io --set configuration.traefik=true --wait" +sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTP=80 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --set configuration.host=k3s-$PUBLIC_HOSTNAME --set configuration.domain=nip.io --set configuration.traefik=true --wait" echo "----- Waiting for k3s.yaml from k3s-edge" until [ -f /home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result} ] do - sudo su - ubuntu bash -c "wget http://$ADVERTISE_IP:80/k3s.yaml.${random_string.k3s_edge_id.result}" + sudo su - ubuntu bash -c "wget --no-check-certificate https://k3s-$PUBLIC_HOSTNAME.nip.io:443/k3s.yaml.${random_string.k3s_edge_id.result}" sleep 5 done echo "----- Adding smarter-edge to k3s-edge" From 0d6e65c18e3b7247d0ae7aade6befa478404d12f Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Fri, 24 Mar 2023 07:48:32 -0500 Subject: [PATCH 17/37] Graviton instance Signed-off-by: Alexandre Peixoto Ferreira --- terraform/k3s/main.tf | 5 ++++- terraform/smarter-main.tf | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/terraform/k3s/main.tf b/terraform/k3s/main.tf index 6ac3b2e..99e52cc 100644 --- a/terraform/k3s/main.tf +++ b/terraform/k3s/main.tf @@ -14,7 +14,10 @@ data "aws_ami" "ubuntu" { filter { name = "name" - values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] + #arm64 + values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-arm64-server-*"] + #x86_64 + #values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] } owners = ["099720109477"] } diff --git a/terraform/smarter-main.tf b/terraform/smarter-main.tf index e7a01d0..89f9277 100644 --- a/terraform/smarter-main.tf +++ b/terraform/smarter-main.tf @@ -3,7 +3,7 @@ provider "aws" { } locals { - deployment_name = "smarter-testing" + deployment_name = "smarter-testing-alex" } data "aws_vpc" "vpc" { @@ -103,13 +103,16 @@ module "k3s" { assign_public_ip = true deployment_name = local.deployment_name - instance_type = "t3a.medium" + #arm64 - Graviton instance + instance_type = "t4g.medium" + #x86_64 - Graviton instance + #instance_type = "t3a.medium" #ami_id = ami-0333305f9719618c7 (ubuntu 22.04 20230115) - #subnet_id = "subnet-xxxxx" #If there is no default subnet + subnet_id = "subnet-0a0e6c54239cf12fc" keypair_content = module.ssh_key_pair.public_key security_group_ids = [aws_security_group.sg.id] kubeconfig_mode = "644" - #letsencrypt_email = "xxx@yyy.com" + letsencrypt_email = "alexandre.ferreira@arm.com" } output "k3s_master_public_dns" { From 9ac5462ea6778f6ec5ab883bf7b04e36fb92c563 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Fri, 24 Mar 2023 09:53:57 -0500 Subject: [PATCH 18/37] Anonymize Signed-off-by: Alexandre Peixoto Ferreira --- terraform/smarter-main.tf | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/terraform/smarter-main.tf b/terraform/smarter-main.tf index 89f9277..cbb9fd8 100644 --- a/terraform/smarter-main.tf +++ b/terraform/smarter-main.tf @@ -3,7 +3,7 @@ provider "aws" { } locals { - deployment_name = "smarter-testing-alex" + deployment_name = "smarter-testing" } data "aws_vpc" "vpc" { @@ -105,14 +105,13 @@ module "k3s" { deployment_name = local.deployment_name #arm64 - Graviton instance instance_type = "t4g.medium" - #x86_64 - Graviton instance + #x86_64 instance #instance_type = "t3a.medium" - #ami_id = ami-0333305f9719618c7 (ubuntu 22.04 20230115) - subnet_id = "subnet-0a0e6c54239cf12fc" + #subnet_id = "subnet-" keypair_content = module.ssh_key_pair.public_key security_group_ids = [aws_security_group.sg.id] kubeconfig_mode = "644" - letsencrypt_email = "alexandre.ferreira@arm.com" + #letsencrypt_email = "xxxx@yyy.com" } output "k3s_master_public_dns" { From 9b95c7b011974a0a41e054fe435c50283ade3be4 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Fri, 24 Mar 2023 13:16:25 -0500 Subject: [PATCH 19/37] A little more info for debugging Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/terraform/README.md b/terraform/README.md index d0cf15d..ca7dd88 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -47,3 +47,14 @@ Terraform expects the following environment variables: AWS_ACCESS_KEY_ID, AWS_SE ## Networking If an error appear about "default subnet not found", a subnet was not defined ias default for the VPC. A subnet can be set in modules "k3s" object. Uncomment the subnet_id and use one valid for your VPC. + +## Debugging information + +Log in to the EC2 machine using the ssh command + +```bash +ssh -i ssh/-prod-k3s.pem ubuntu@ +``` + +Please take a look at the log at /var/log/cloud-init-output.log and /var/log/cloud-init.log at the EC2 machine to determine where the program failed +The script that is executed is called part-002 From 77378f172794a74adfcd66a6a48500c0e886c668 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Wed, 29 Mar 2023 13:22:16 -0500 Subject: [PATCH 20/37] Add letsencrypt_email as variable Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 5 ++--- terraform/smarter-main.tf | 12 +++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index ca7dd88..a9395fd 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -7,12 +7,11 @@ deployment-name (locals): terraform name for this deployment, also used for helm ## Running -Update the smarter-main.tf variables: deployment-name and letsencrypt_email to be valid Run the following commands ``` terraform init -# optional: terraform plan -terraform apply +# optional: terraform plan -var "letsencrypt_email=" +terraform apply -var "letsencrypt_email=" ``` ## Outputs diff --git a/terraform/smarter-main.tf b/terraform/smarter-main.tf index cbb9fd8..e8894b0 100644 --- a/terraform/smarter-main.tf +++ b/terraform/smarter-main.tf @@ -3,7 +3,7 @@ provider "aws" { } locals { - deployment_name = "smarter-testing" + deployment_name = "smarter-testing-alex" } data "aws_vpc" "vpc" { @@ -107,11 +107,11 @@ module "k3s" { instance_type = "t4g.medium" #x86_64 instance #instance_type = "t3a.medium" - #subnet_id = "subnet-" + #subnet_id = "subnet-xxxxxx" keypair_content = module.ssh_key_pair.public_key security_group_ids = [aws_security_group.sg.id] kubeconfig_mode = "644" - #letsencrypt_email = "xxxx@yyy.com" + letsencrypt_email = var.letsencrypt_email } output "k3s_master_public_dns" { @@ -123,3 +123,9 @@ output "k3s_edge" { value = module.k3s.k3s_edge.result description = "System-wide password: grafana admin, k3s-edge ID" } + +variable "letsencrypt_email" { + type = string + description = "email to be used in let's encrypt" +} + From 67d8c641af5248c54aa287d844ecec2d76ea7abb Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Wed, 29 Mar 2023 14:42:31 -0500 Subject: [PATCH 21/37] Add timing and logging information Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/terraform/README.md b/terraform/README.md index a9395fd..90d599f 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -14,6 +14,12 @@ terraform init terraform apply -var "letsencrypt_email=" ``` +Please observe that the full installation of k3s, helm charts in the EC2 instance can take up to 15min (expected around 10min) with various parts of the system being available at different times. If it is desired to follow the installation the command below will print the current log and follow it + +```bash +ssh -i ssh/-prod-k3s.pem ubuntu@ "tail -f /var/log/cloud-init-output.log" +``` + ## Outputs Terraform will output the name of EC2 instance allocated and password/ID generated by terraform. From f5316d8ecc06f8674f7df9389bc7e879162648c1 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Wed, 29 Mar 2023 16:33:11 -0500 Subject: [PATCH 22/37] Add link to main README for terraform Signed-off-by: Alexandre Peixoto Ferreira --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index d2f5429..a015c64 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,13 @@ In this guide we assume you have done the following: - The node must be able to reach your k3s-edge-server and cloud-data-node via IP ## Deploy demo + +### Deploy using terraform + +If you have an AWS account, a terraform script is available on this repository at [Terraform readme](terraform/README.md). This script will allocate an AWS EC2 Graviton instance, install k3s and helm and install all the charts needed to run this demo. The only missing part is one or more edge nodes that the user needs to provide. + +### Step by step deployment + - To deploy the base system components common to all edge nodes, as well as the demo applications, we opt to use **Helm v3**. To install helm on the device which you are managing your k3s edge cluster with, you can follow the guide [here](https://helm.sh/docs/intro/install/#from-script). - Ensure in your environment that your kubeconfig is set properly. As a quick sanity check you can run: ```bash From 93643ef75adc8ac5c2084b8a08a46bee0b4bbfac Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Wed, 29 Mar 2023 16:53:22 -0500 Subject: [PATCH 23/37] Add more external variables Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 11 ++++++++++- terraform/smarter-main.tf | 29 ++++++++++++++++++----------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index 90d599f..8b330a9 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -3,11 +3,20 @@ It assumes that the enviroment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN are set correctly so terraform can access AWS. Set the following variables to correct values: region (provider "aws): AWS region to allocate an EC2 instance on. -deployment-name (locals): terraform name for this deployment, also used for helm + +Required variables: + +*letsencrypt\_email + +Optional variables: + +* deployment\_name: Prefix to apply to object names. +* AWS\_EC2\_instance\_type: instance type to be used ## Running Run the following commands + ``` terraform init # optional: terraform plan -var "letsencrypt_email=" diff --git a/terraform/smarter-main.tf b/terraform/smarter-main.tf index e8894b0..407b2a2 100644 --- a/terraform/smarter-main.tf +++ b/terraform/smarter-main.tf @@ -2,17 +2,13 @@ provider "aws" { region = "eu-west-1" } -locals { - deployment_name = "smarter-testing-alex" -} - data "aws_vpc" "vpc" { # This assumes that there is a default VPC default = true } resource "aws_security_group" "sg" { - name = "allow-${local.deployment_name}" + name = "allow-${var.deployment_name}" vpc_id = data.aws_vpc.vpc.id ingress { @@ -78,14 +74,14 @@ resource "aws_security_group" "sg" { } tags = { - Name = "allow-${local.deployment_name}" + Name = "allow-${var.deployment_name}" } } module "ssh_key_pair" { # tflint-ignore: terraform_module_pinned_source source = "git::https://github.com/cloudposse/terraform-aws-key-pair.git?ref=master" - namespace = local.deployment_name + namespace = var.deployment_name stage = "prod" name = "k3s" ssh_public_key_path = "ssh" @@ -102,11 +98,9 @@ module "k3s" { } assign_public_ip = true - deployment_name = local.deployment_name - #arm64 - Graviton instance - instance_type = "t4g.medium" + deployment_name = var.deployment_name + instance_type = var.AWS_EC2_instance_type #x86_64 instance - #instance_type = "t3a.medium" #subnet_id = "subnet-xxxxxx" keypair_content = module.ssh_key_pair.public_key security_group_ids = [aws_security_group.sg.id] @@ -129,3 +123,16 @@ variable "letsencrypt_email" { description = "email to be used in let's encrypt" } +variable "AWS_EC2_instance_type" { + type = string + description = "instance type to be used, default is a graviton t4g.medium" + #instance_type = "t3a.medium" for x86 + default = "t4g.medium" +} + +variable "deployment_name" { + type = string + description = "Prefix applied to all objects created by this terraform" + default = "smarter-testing" +} + From 4bd83f871d2132fe7d5bc7f811dbee0e00bd1afa Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Wed, 29 Mar 2023 17:15:41 -0500 Subject: [PATCH 24/37] Add AWS_VPC_subnet_id as variable Signed-off-by: Alexandre Peixoto Ferreira --- terraform/smarter-main.tf | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/terraform/smarter-main.tf b/terraform/smarter-main.tf index 407b2a2..2406506 100644 --- a/terraform/smarter-main.tf +++ b/terraform/smarter-main.tf @@ -101,7 +101,7 @@ module "k3s" { deployment_name = var.deployment_name instance_type = var.AWS_EC2_instance_type #x86_64 instance - #subnet_id = "subnet-xxxxxx" + subnet_id = var.AWS_VPC_subnet_id keypair_content = module.ssh_key_pair.public_key security_group_ids = [aws_security_group.sg.id] kubeconfig_mode = "644" @@ -136,3 +136,9 @@ variable "deployment_name" { default = "smarter-testing" } +variable "AWS_VPC_subnet_id" { + type = string + description = "subnet_id use the default of the VPC if this is not defined" + default = "" +} + From 3e9c112b1072a3ede9d3d25e7a7514fcc6afe4f4 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Thu, 30 Mar 2023 14:25:02 -0500 Subject: [PATCH 25/37] Changed domain to .nip.io so Let's encrypt applies to the whole domain Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 4 ++-- terraform/k3s/main.tf | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index 8b330a9..154f73f 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -33,7 +33,7 @@ ssh -i ssh/-prod-k3s.pem ubuntu@ "tail Terraform will output the name of EC2 instance allocated and password/ID generated by terraform. -Grafana web interface instance on EC2 (k3s cloud) can be accessed by https://grafana-.nip.io with user admin and password . +Grafana web interface instance on EC2 (k3s cloud) can be accessed by https://grafana..nip.io with user admin and password . A ssh directory will be created locally containing a private/public SSH key that can be used to access the instance using the following command: @@ -48,7 +48,7 @@ Helm was used to install charts and can be used to manage them by setting the co The edge devices can be installed (Raspberry pi4 for example) by running the following script. The command will install k3s agent and connect that to the k3s edge running on the ec2 instanct. The command will install k3s agent and connect that to the k3s edge running on the ec2 instance. ``` -wget https://k3s-<.nip.io/k3s-start.sh. | bash -s - +wget https://k3s.<.nip.io/k3s-start.sh. | bash -s - ``` # Troubleshooting diff --git a/terraform/k3s/main.tf b/terraform/k3s/main.tf index 99e52cc..c890a0e 100644 --- a/terraform/k3s/main.tf +++ b/terraform/k3s/main.tf @@ -69,13 +69,13 @@ do sleep 5 done echo "----- Adding smarter-cloud to k3s" -sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=grafana-$PUBLIC_HOSTNAME --set domain=nip.io --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" +sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=grafana --set domain=$PUBLIC_HOSTNAME.nip.io --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" echo "----- Adding smarter-edge to k3s" -sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTP=80 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --set configuration.host=k3s-$PUBLIC_HOSTNAME --set configuration.domain=nip.io --set configuration.traefik=true --wait" +sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTP=80 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --set configuration.host=k3s --set configuration.domain=$PUBLIC_HOSTNAME.nip.io --set configuration.traefik=true --wait" echo "----- Waiting for k3s.yaml from k3s-edge" until [ -f /home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result} ] do - sudo su - ubuntu bash -c "wget --no-check-certificate https://k3s-$PUBLIC_HOSTNAME.nip.io:443/k3s.yaml.${random_string.k3s_edge_id.result}" + sudo su - ubuntu bash -c "wget --no-check-certificate https://k3s.$PUBLIC_HOSTNAME.nip.io:443/k3s.yaml.${random_string.k3s_edge_id.result}" sleep 5 done echo "----- Adding smarter-edge to k3s-edge" From 154c03ceef2e2c01172ebd003199c438af343e21 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Thu, 30 Mar 2023 15:51:32 -0500 Subject: [PATCH 26/37] Add the extra variables Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/terraform/README.md b/terraform/README.md index 154f73f..81f2aec 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -12,6 +12,7 @@ Optional variables: * deployment\_name: Prefix to apply to object names. * AWS\_EC2\_instance\_type: instance type to be used +* AWS_VPC_subnet_id: subnet_id use the default of the VPC if this is not defined" ## Running From 8109c18256d8f32fe9afa945406ce85fabdb99e4 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Fri, 31 Mar 2023 07:52:07 -0500 Subject: [PATCH 27/37] FIx _ and < characters on README (markdown) Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index 81f2aec..19394ff 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -1,6 +1,6 @@ # Terrform script to install smarter on AWS EC2 -It assumes that the enviroment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN are set correctly so terraform can access AWS. +It assumes that the enviroment variables AWS\_ACCESS\_KEY\_ID, AWS\_SECRET\_ACCESS\_KEY and AWS\_SESSION\_TOKEN are set correctly so terraform can access AWS. Set the following variables to correct values: region (provider "aws): AWS region to allocate an EC2 instance on. @@ -12,7 +12,7 @@ Optional variables: * deployment\_name: Prefix to apply to object names. * AWS\_EC2\_instance\_type: instance type to be used -* AWS_VPC_subnet_id: subnet_id use the default of the VPC if this is not defined" +* AWS\_VPC\_subnet\_id: subnet_id use the default of the VPC if this is not defined ## Running @@ -34,7 +34,7 @@ ssh -i ssh/-prod-k3s.pem ubuntu@ "tail Terraform will output the name of EC2 instance allocated and password/ID generated by terraform. -Grafana web interface instance on EC2 (k3s cloud) can be accessed by https://grafana..nip.io with user admin and password . +Grafana web interface instance on EC2 (k3s cloud) can be accessed by https://grafana.\.nip.io with user admin and password \. A ssh directory will be created locally containing a private/public SSH key that can be used to access the instance using the following command: @@ -43,7 +43,7 @@ ssh -i ssh/-prod-k3s.pem ubuntu@ ``` on the instance access to k3s cloud (running the cloud containers) can be accessed by setting KUBECONFIG to /etc/rancher/k3s/k3s.yaml -The k3s edge, that manages the edge devices and applications running on them, can be accessd by setting KUBECONFIG as $(pwd)/k3s.yaml.< +The k3s edge, that manages the edge devices and applications running on them, can be accessd by setting KUBECONFIG as $(pwd)/k3s.yaml.\ Helm was used to install charts and can be used to manage them by setting the correct KUBECONFIG @@ -57,11 +57,11 @@ wget https://k3s.<.nip.io/k3s-start.sh.< ## AWS authentication Use the AWS credentials provided in the "Get credentials for ProjAdmins" page. -Terraform expects the following environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN. +_Terraform expects the following environment variables: AWS\_ACCESS\_KEY\_ID, AWS\_SECRET\_ACCESS\_KEY and AWS\_SESSION\_TOKEN. ## Networking -If an error appear about "default subnet not found", a subnet was not defined ias default for the VPC. A subnet can be set in modules "k3s" object. Uncomment the subnet_id and use one valid for your VPC. +If an error is reported about "default subnet not found", a subnet was not defined as default for the VPC. A subnet can be set using the AWS\_VPC\_subnet\_id variable. ## Debugging information From fde5a18784b3566387dd2a12c41df04d9f4affd3 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Fri, 31 Mar 2023 08:39:24 -0500 Subject: [PATCH 28/37] Syntax fixes Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index 19394ff..86b2bbc 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -1,12 +1,12 @@ -# Terrform script to install smarter on AWS EC2 +# Terraform script to install smarter on AWS EC2 -It assumes that the enviroment variables AWS\_ACCESS\_KEY\_ID, AWS\_SECRET\_ACCESS\_KEY and AWS\_SESSION\_TOKEN are set correctly so terraform can access AWS. +It assumes that the environment variables AWS\_ACCESS\_KEY\_ID, AWS\_SECRET\_ACCESS\_KEY and AWS\_SESSION\_TOKEN are set correctly so Terraform can access AWS. Set the following variables to correct values: -region (provider "aws): AWS region to allocate an EC2 instance on. +region (provider "aws"): AWS region to allocate an EC2 instance on. Required variables: -*letsencrypt\_email +* letsencrypt\_email Optional variables: @@ -32,9 +32,9 @@ ssh -i ssh/-prod-k3s.pem ubuntu@ "tail ## Outputs -Terraform will output the name of EC2 instance allocated and password/ID generated by terraform. +Terraform will output the name of EC2 instance allocated and password/ID generated by Terraform. -Grafana web interface instance on EC2 (k3s cloud) can be accessed by https://grafana.\.nip.io with user admin and password \. +Grafana web interface can be accessed by https://grafana.\.nip.io with user admin and password \. A ssh directory will be created locally containing a private/public SSH key that can be used to access the instance using the following command: @@ -42,12 +42,13 @@ A ssh directory will be created locally containing a private/public SSH key that ssh -i ssh/-prod-k3s.pem ubuntu@ ``` -on the instance access to k3s cloud (running the cloud containers) can be accessed by setting KUBECONFIG to /etc/rancher/k3s/k3s.yaml -The k3s edge, that manages the edge devices and applications running on them, can be accessd by setting KUBECONFIG as $(pwd)/k3s.yaml.\ +K3s cloud access on the instance (running the cloud containers) can be achieved by setting KUBECONFIG to /etc/rancher/k3s/k3s.yaml. It should be already be set for the ubuntu user at the end of the installation. +K3s edge, that manages the edge devices and applications running on them, can be accessed by setting KUBECONFIG as $(pwd)/k3s.yaml.\, that also will be available at the end of the installation. -Helm was used to install charts and can be used to manage them by setting the correct KUBECONFIG +Helm was used to install charts and can be used to manage them by setting the correct KUBECONFIG. + +The edge devices can be installed (Raspberry pi4 for example) by running the following script. The script will install a k3s agent and configure that agent to be a node for k3s edge running on the EC2 instance. -The edge devices can be installed (Raspberry pi4 for example) by running the following script. The command will install k3s agent and connect that to the k3s edge running on the ec2 instanct. The command will install k3s agent and connect that to the k3s edge running on the ec2 instance. ``` wget https://k3s.<.nip.io/k3s-start.sh. | bash -s - ``` From 904b9fb6e99e27bc59271eba056fed0d3a10a9f0 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 4 Apr 2023 07:26:08 -0500 Subject: [PATCH 29/37] Add figure and more description Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 5 +++++ terraform/SMARTER_example.png | Bin 0 -> 77644 bytes 2 files changed, 5 insertions(+) create mode 100644 terraform/SMARTER_example.png diff --git a/terraform/README.md b/terraform/README.md index 86b2bbc..dfc2561 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -1,5 +1,10 @@ # Terraform script to install smarter on AWS EC2 +This script installs SMARTER example using helm charts into one AWS EC2 instance. + +This figure shows the components of the application and where they reside. +![SMARTER](SMARTER_example.png) + It assumes that the environment variables AWS\_ACCESS\_KEY\_ID, AWS\_SECRET\_ACCESS\_KEY and AWS\_SESSION\_TOKEN are set correctly so Terraform can access AWS. Set the following variables to correct values: region (provider "aws"): AWS region to allocate an EC2 instance on. diff --git a/terraform/SMARTER_example.png b/terraform/SMARTER_example.png new file mode 100644 index 0000000000000000000000000000000000000000..0c2dca8eba8328f609ddad0ec51f551c26c68be9 GIT binary patch literal 77644 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�S7=J4JTa^$Sw;y*?VB^S;}( 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 z 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 Date: Tue, 4 Apr 2023 08:40:20 -0500 Subject: [PATCH 30/37] Fix reference to Terraform script Signed-off-by: Alexandre Peixoto Ferreira --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a015c64..dd5af7b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ In this guide we assume you have done the following: ### Deploy using terraform -If you have an AWS account, a terraform script is available on this repository at [Terraform readme](terraform/README.md). This script will allocate an AWS EC2 Graviton instance, install k3s and helm and install all the charts needed to run this demo. The only missing part is one or more edge nodes that the user needs to provide. +If you have an AWS account, a terraform script is available on this repository at [Terraform](terraform) and a [readme](terraform/README.md) is available. This script will allocate an AWS EC2 Graviton instance, install k3s and helm and install all the charts needed to run this demo. The only missing part is one or more edge nodes that the user needs to provide. ### Step by step deployment From fb715d529d0614d0d8eb0ef2cf45726ea3eaa751 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 4 Apr 2023 10:50:54 -0500 Subject: [PATCH 31/37] Rewrite main README to be clear on installation using terraform Signed-off-by: Alexandre Peixoto Ferreira --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd5af7b..d30c9c8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # SMARTER Demo Deployment Instructions [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/smarter)](https://artifacthub.io/packages/search?repo=smarter) -## This demo makes the following assumptions about your environment + +The demo can be deployed by using the terraform script on this repository [Terraform](terraform) and following the [readme](terraform/README.md). It is also described on the section "Deploy using terraform" below. + +## This demo makes the following assumptions about your environment if deployed using helm charts In this guide we assume you have done the following: - You should have an installed InfluxDB and Grafana instance in a separate kubernetes cluster (cloud or local). @@ -32,7 +35,7 @@ In this guide we assume you have done the following: ### Deploy using terraform -If you have an AWS account, a terraform script is available on this repository at [Terraform](terraform) and a [readme](terraform/README.md) is available. This script will allocate an AWS EC2 Graviton instance, install k3s and helm and install all the charts needed to run this demo. The only missing part is one or more edge nodes that the user needs to provide. +If you have an AWS account, a terraform script is available on this repository at [Terraform](terraform) and a [readme](terraform/README.md) describes how to use it. This script will allocate an AWS EC2 Graviton instance, install k3s and helm and install all the charts needed to run this demo. The only missing part is one or more edge nodes that the user needs to provide. ### Step by step deployment From 94c6dfca73df83542ca193b4d4a551b479945604 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 4 Apr 2023 10:56:03 -0500 Subject: [PATCH 32/37] Fix README to put terraform first Signed-off-by: Alexandre Peixoto Ferreira --- README.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d30c9c8..5f1b507 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,13 @@ The demo can be deployed by using the terraform script on this repository [Terraform](terraform) and following the [readme](terraform/README.md). It is also described on the section "Deploy using terraform" below. -## This demo makes the following assumptions about your environment if deployed using helm charts +## Deploy using terraform + +If you have an AWS account, a terraform script is available on this repository at [Terraform](terraform) and a [readme](terraform/README.md) describes how to use it. This script will allocate an AWS EC2 Graviton instance, install k3s and helm and install all the charts needed to run this demo. The only missing part is one or more edge nodes that the user needs to provide. + +## Step by step deployment + +### This demo makes the following assumptions about your environment if deployed using helm charts In this guide we assume you have done the following: - You should have an installed InfluxDB and Grafana instance in a separate kubernetes cluster (cloud or local). @@ -31,14 +37,6 @@ In this guide we assume you have done the following: - You must be able to reach your edge node via IP on ports `22`(ssh) and `2520`(Webserver) from your dev machine for parts of this demo to work - The node must be able to reach your k3s-edge-server and cloud-data-node via IP -## Deploy demo - -### Deploy using terraform - -If you have an AWS account, a terraform script is available on this repository at [Terraform](terraform) and a [readme](terraform/README.md) describes how to use it. This script will allocate an AWS EC2 Graviton instance, install k3s and helm and install all the charts needed to run this demo. The only missing part is one or more edge nodes that the user needs to provide. - -### Step by step deployment - - To deploy the base system components common to all edge nodes, as well as the demo applications, we opt to use **Helm v3**. To install helm on the device which you are managing your k3s edge cluster with, you can follow the guide [here](https://helm.sh/docs/intro/install/#from-script). - Ensure in your environment that your kubeconfig is set properly. As a quick sanity check you can run: ```bash From 0e7419585c5a74a4576ea31a3bff7d61109d87ec Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 4 Apr 2023 11:24:36 -0500 Subject: [PATCH 33/37] Add support for tfvars on README and a template Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 14 +++++++++++++- terraform/smarter-variables.tfvars.template | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 terraform/smarter-variables.tfvars.template diff --git a/terraform/README.md b/terraform/README.md index dfc2561..a7d4087 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -21,7 +21,17 @@ Optional variables: ## Running -Run the following commands +An smarter-variables.tfvars.template is provided that can be copied so all the variables are set in this file and referenced by the option -var-file="smarter-variables.tfvars" where smarter-variables.tfvars is the name of the file used to set the variables. Commented variables are ignored. + +### Run the following commands if using the smarter-variables.tfvars optionn + +``` +terraform init +# optional: terraform plan -var-file="smarter-variables.tfvars" +terraform apply -var-file="smarter-variables.tfvars" +``` + +### Run the following commands if setting the variables on the command line ``` terraform init @@ -29,6 +39,8 @@ terraform init terraform apply -var "letsencrypt_email=" ``` +## Checking status of installation + Please observe that the full installation of k3s, helm charts in the EC2 instance can take up to 15min (expected around 10min) with various parts of the system being available at different times. If it is desired to follow the installation the command below will print the current log and follow it ```bash diff --git a/terraform/smarter-variables.tfvars.template b/terraform/smarter-variables.tfvars.template new file mode 100644 index 0000000..4c29ac4 --- /dev/null +++ b/terraform/smarter-variables.tfvars.template @@ -0,0 +1,4 @@ +#letsencrypt_email = "xxxx@yyy.com" +#AWS_EC2_instance_type = "t4g.medium" +#deployment_name = "smarter-testing" +#AWS_VPC_subnet_id = "subnet-id-xxxx" From 3e728cdaa3463c8824fa40dd06a01d1f99cd74b6 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 4 Apr 2023 13:05:20 -0500 Subject: [PATCH 34/37] Change to template.tfvars Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 6 +++--- .../{smarter-variables.tfvars.template => template.tfvars} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename terraform/{smarter-variables.tfvars.template => template.tfvars} (100%) diff --git a/terraform/README.md b/terraform/README.md index a7d4087..396553a 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -21,7 +21,7 @@ Optional variables: ## Running -An smarter-variables.tfvars.template is provided that can be copied so all the variables are set in this file and referenced by the option -var-file="smarter-variables.tfvars" where smarter-variables.tfvars is the name of the file used to set the variables. Commented variables are ignored. +An template.tfvars is provided that can be copied so all the variables are set in this file and referenced by the option -var-file="smarter-variables.tfvars" where smarter-variables.tfvars is the name of the file used to set the variables. Commented variables are ignored. ### Run the following commands if using the smarter-variables.tfvars optionn @@ -67,7 +67,7 @@ Helm was used to install charts and can be used to manage them by setting the co The edge devices can be installed (Raspberry pi4 for example) by running the following script. The script will install a k3s agent and configure that agent to be a node for k3s edge running on the EC2 instance. ``` -wget https://k3s.<.nip.io/k3s-start.sh. | bash -s - +wget https://k3s..nip.io/k3s-start.sh. | bash -s - ``` # Troubleshooting @@ -75,7 +75,7 @@ wget https://k3s.<.nip.io/k3s-start.sh.< ## AWS authentication Use the AWS credentials provided in the "Get credentials for ProjAdmins" page. -_Terraform expects the following environment variables: AWS\_ACCESS\_KEY\_ID, AWS\_SECRET\_ACCESS\_KEY and AWS\_SESSION\_TOKEN. +Terraform expects the following environment variables: AWS\_ACCESS\_KEY\_ID, AWS\_SECRET\_ACCESS\_KEY and AWS\_SESSION\_TOKEN. ## Networking diff --git a/terraform/smarter-variables.tfvars.template b/terraform/template.tfvars similarity index 100% rename from terraform/smarter-variables.tfvars.template rename to terraform/template.tfvars From 6fe2cd7d4d8d2ddc6c6984acb34545b4cd89681a Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 4 Apr 2023 15:07:00 -0500 Subject: [PATCH 35/37] Change smarter-k3s-edge to be able to be embedded in another website Signed-off-by: Alexandre Peixoto Ferreira --- charts/smarter-k3s-edge/Chart.yaml | 2 +- charts/smarter-k3s-edge/templates/common.yaml | 10 +++++----- .../smarter-k3s-edge/templates/k3s-edge-ingress.yaml | 4 ++-- charts/smarter-k3s-edge/values.yaml | 2 ++ 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/charts/smarter-k3s-edge/Chart.yaml b/charts/smarter-k3s-edge/Chart.yaml index db8d200..2c77697 100644 --- a/charts/smarter-k3s-edge/Chart.yaml +++ b/charts/smarter-k3s-edge/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: smarter-k3s-edge -version: 0.0.9 +version: 0.0.10 type: application appVersion: v1.25.3-k3s1 description: K3s server on kubernetes diff --git a/charts/smarter-k3s-edge/templates/common.yaml b/charts/smarter-k3s-edge/templates/common.yaml index 0ea1434..1df3e70 100644 --- a/charts/smarter-k3s-edge/templates/common.yaml +++ b/charts/smarter-k3s-edge/templates/common.yaml @@ -81,7 +81,7 @@ data: add_header X-Content-Type-Options nosniff; root /var/www/html; server_name _; - location / { + location {{ .Values.configuration.wwwpath }} { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; @@ -150,14 +150,14 @@ data: echo -e "US\n\n\nSmarter\n\n"{{ default .Values.configuration.hostIP .Values.configuration.externalHostIP }}"\n\n" | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 {{- end }} - mkdir -p /var/www/html + mkdir -p /var/www/html{{ .Values.configuration.wwwpath }} until [ -f /etc/rancher/k3s/k3s.yaml ] do sleep 1 done - sed -e "s/127\.0\.0\.1/"{{ default .Values.configuration.hostIP .Values.configuration.externalHostIP }}"/" /etc/rancher/k3s/k3s.yaml > /var/www/html/k3s.yaml.{{ .Values.configuration.id }} - ln -s /var/lib/rancher/k3s/server/token /var/www/html/token.{{ .Values.configuration.id }} - ln -s /etc/nginx/conf.d/k3s-start.sh /var/www/html/k3s-start.sh.{{ .Values.configuration.id }} + sed -e "s/127\.0\.0\.1/"{{ default .Values.configuration.hostIP .Values.configuration.externalHostIP }}"/" /etc/rancher/k3s/k3s.yaml > /var/www/html{{ .Values.configuration.wwwpath }}k3s.yaml.{{ .Values.configuration.id }} + ln -s /var/lib/rancher/k3s/server/token /var/www/html{{ .Values.configuration.wwwpath }}token.{{ .Values.configuration.id }} + ln -s /etc/nginx/conf.d/k3s-start.sh /var/www/html{{ .Values.configuration.wwwpath }}k3s-start.sh.{{ .Values.configuration.id }} chmod -R ago+rw /var/www/html nginx -c /etc/nginx/conf.d/default.conf -g 'daemon off;' --- diff --git a/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml b/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml index 7ae576b..8408423 100644 --- a/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml +++ b/charts/smarter-k3s-edge/templates/k3s-edge-ingress.yaml @@ -11,7 +11,7 @@ spec: - host: {{ .Values.configuration.host }}.{{ .Values.configuration.domain }} http: paths: - - path: / + - path: {{ .Values.configuration.wwwpath }} pathType: Prefix backend: service: @@ -19,7 +19,7 @@ spec: port: number: {{ .Values.configuration.portHTTP }} tls: - - secretName: {{ .Values.application.appName }}-tls + - secretName: {{ .Values.configuration.certificateID }} hosts: - {{ .Values.configuration.host }}.{{ .Values.configuration.domain }} --- diff --git a/charts/smarter-k3s-edge/values.yaml b/charts/smarter-k3s-edge/values.yaml index 497124e..98dcff9 100644 --- a/charts/smarter-k3s-edge/values.yaml +++ b/charts/smarter-k3s-edge/values.yaml @@ -23,3 +23,5 @@ configuration: #traefik: True # Uncomment to enable labeling for smarter-demo #smarter_demo_labels: true + wwwpath: / + certificateID: "{{ .Values.application.appName }}-tls" From 61cb5b25350145a6233e35c46c6c58da91c6c7bf Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 4 Apr 2023 15:16:54 -0500 Subject: [PATCH 36/37] Fix terraform to use grafana instead of k3s website Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 9 ++++++++- terraform/k3s/main.tf | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index 396553a..91ea95f 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -67,7 +67,14 @@ Helm was used to install charts and can be used to manage them by setting the co The edge devices can be installed (Raspberry pi4 for example) by running the following script. The script will install a k3s agent and configure that agent to be a node for k3s edge running on the EC2 instance. ``` -wget https://k3s..nip.io/k3s-start.sh. | bash -s - +wget https://grafana..nip.io//k3s/k3s-start.sh. | bash -s - +``` + +Token and k3s.yaml file can be accessed by: + +``` +wget https://grafana..nip.io//k3s/token. | bash -s - +wget https://grafana..nip.io//k3s/k3s.yaml. | bash -s - ``` # Troubleshooting diff --git a/terraform/k3s/main.tf b/terraform/k3s/main.tf index c890a0e..8bf992d 100644 --- a/terraform/k3s/main.tf +++ b/terraform/k3s/main.tf @@ -71,11 +71,11 @@ done echo "----- Adding smarter-cloud to k3s" sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=grafana --set domain=$PUBLIC_HOSTNAME.nip.io --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" echo "----- Adding smarter-edge to k3s" -sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTP=80 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --set configuration.host=k3s --set configuration.domain=$PUBLIC_HOSTNAME.nip.io --set configuration.traefik=true --wait" +sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTP=80 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --set configuration.host=grafana --set configuration.domain=$PUBLIC_HOSTNAME.nip.io --set configuration.traefik=true --set configuration.certificateID=my-smartercloud-grafana-tls --set configuration.wwwpath=/k3s/ --wait" echo "----- Waiting for k3s.yaml from k3s-edge" until [ -f /home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result} ] do - sudo su - ubuntu bash -c "wget --no-check-certificate https://k3s.$PUBLIC_HOSTNAME.nip.io:443/k3s.yaml.${random_string.k3s_edge_id.result}" + sudo su - ubuntu bash -c "wget --no-check-certificate https://grafana.$PUBLIC_HOSTNAME.nip.io/k3s/k3s.yaml.${random_string.k3s_edge_id.result}" sleep 5 done echo "----- Adding smarter-edge to k3s-edge" From eb8367028673a075824b7994e3219b03073dfc03 Mon Sep 17 00:00:00 2001 From: Alexandre Peixoto Ferreira Date: Tue, 4 Apr 2023 21:27:27 -0500 Subject: [PATCH 37/37] Change to sslip.io and be a little more resilient Signed-off-by: Alexandre Peixoto Ferreira --- terraform/README.md | 8 ++++---- terraform/k3s/main.tf | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index 91ea95f..ffb7db3 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -51,7 +51,7 @@ ssh -i ssh/-prod-k3s.pem ubuntu@ "tail Terraform will output the name of EC2 instance allocated and password/ID generated by Terraform. -Grafana web interface can be accessed by https://grafana.\.nip.io with user admin and password \. +Grafana web interface can be accessed by https://grafana.\.sslip.io with user admin and password \. A ssh directory will be created locally containing a private/public SSH key that can be used to access the instance using the following command: @@ -67,14 +67,14 @@ Helm was used to install charts and can be used to manage them by setting the co The edge devices can be installed (Raspberry pi4 for example) by running the following script. The script will install a k3s agent and configure that agent to be a node for k3s edge running on the EC2 instance. ``` -wget https://grafana..nip.io//k3s/k3s-start.sh. | bash -s - +wget https://grafana..sslip.io//k3s/k3s-start.sh. | bash -s - ``` Token and k3s.yaml file can be accessed by: ``` -wget https://grafana..nip.io//k3s/token. | bash -s - -wget https://grafana..nip.io//k3s/k3s.yaml. | bash -s - +wget https://grafana..sslip.io//k3s/token. | bash -s - +wget https://grafana..sslip.io//k3s/k3s.yaml. | bash -s - ``` # Troubleshooting diff --git a/terraform/k3s/main.tf b/terraform/k3s/main.tf index 8bf992d..a2a3e08 100644 --- a/terraform/k3s/main.tf +++ b/terraform/k3s/main.tf @@ -69,13 +69,24 @@ do sleep 5 done echo "----- Adding smarter-cloud to k3s" -sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=grafana --set domain=$PUBLIC_HOSTNAME.nip.io --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" +sudo su - ubuntu bash -c "helm repo add smarter https://smarter-project.github.io/documentation;helm install my-smartercloud smarter/smarter-cloud --set email=${var.letsencrypt_email} --set host=grafana --set domain=$PUBLIC_HOSTNAME.sslip.io --set prometheus.grafana.adminPassword=${random_string.k3s_edge_id.result} --wait" +echo "----- Checking if TLS certificate was generated" +until [ ! -z "$(kubectl get secret/my-smartercloud-grafana-tls 2>/dev/null)" ] +do + echo "Certificate not generated yet, wait 5 seconds and test again" + sleep 5 +done echo "----- Adding smarter-edge to k3s" -sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTP=80 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --set configuration.host=grafana --set configuration.domain=$PUBLIC_HOSTNAME.nip.io --set configuration.traefik=true --set configuration.certificateID=my-smartercloud-grafana-tls --set configuration.wwwpath=/k3s/ --wait" +sudo su - ubuntu bash -c "helm install my-smartercloud-edge smarter/smarter-k3s-edge --set configuration.externalHostIP=$ADVERTISE_IP --set configuration.hostIP=$LOCAL_IP --set configuration.port=6444 --set configuration.portHTTP=80 --set configuration.id='${random_string.k3s_edge_id.result}' --set configuration.smarter_demo_labels=true --set configuration.host=grafana --set configuration.domain=$PUBLIC_HOSTNAME.sslip.io --set configuration.traefik=true --set configuration.certificateID=my-smartercloud-grafana-tls --set configuration.wwwpath=/k3s/ --wait" echo "----- Waiting for k3s.yaml from k3s-edge" until [ -f /home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result} ] do - sudo su - ubuntu bash -c "wget --no-check-certificate https://grafana.$PUBLIC_HOSTNAME.nip.io/k3s/k3s.yaml.${random_string.k3s_edge_id.result}" + sudo su - ubuntu bash -c "wget --no-check-certificate https://grafana.$PUBLIC_HOSTNAME.sslip.io/k3s/k3s.yaml.${random_string.k3s_edge_id.result}" + if [ -z "$(grep 'kind: Config' /home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result})" ] + then + echo "Received a file but it is not a k3s.yaml file, removing" + rm /home/ubuntu/k3s.yaml.${random_string.k3s_edge_id.result} + fi sleep 5 done echo "----- Adding smarter-edge to k3s-edge"