From 54620c225f4208d104bf1a098929fd0dd8167a4c Mon Sep 17 00:00:00 2001 From: AJ0070 Date: Tue, 16 Jun 2026 11:56:01 +0530 Subject: [PATCH] [sifnode] Add AKS deployment scaffolding and Azure provider support --- .gitignore | 6 +- deploy/helm/sifnode/.helmignore | 18 +++ deploy/helm/sifnode/Chart.yaml | 6 + deploy/helm/sifnode/templates/_helpers.tpl | 43 +++++++ .../sifnode/templates/serviceaccount.yaml | 12 ++ .../sifnode/templates/sifnode-configmap.yml | 26 ++++ .../sifnode/templates/sifnode-deployment.yaml | 111 ++++++++++++++++ deploy/helm/sifnode/templates/sifnode-pvc.yml | 23 ++++ .../helm/sifnode/templates/sifnode-role.yml | 12 ++ .../sifnode/templates/sifnode-rolebinding.yml | 16 +++ .../helm/sifnode/templates/sifnode-secret.yml | 7 + .../sifnode/templates/sifnode-service.yaml | 30 +++++ deploy/helm/sifnode/values.yaml | 56 ++++++++ deploy/rake/cluster.rake | 119 +++++++++++++++++ deploy/rake/helpers.rake | 121 ++++++++++++++++++ deploy/terraform/providers/aws/main.tf | 65 ++++++++++ deploy/terraform/providers/aws/outputs.tf | 4 + deploy/terraform/providers/aws/variables.tf | 63 +++++++++ deploy/terraform/providers/azure/main.tf | 63 +++++++++ deploy/terraform/providers/azure/outputs.tf | 13 ++ deploy/terraform/providers/azure/variables.tf | 61 +++++++++ deploy/terraform/template/aws/.envrc.tpl | 1 + deploy/terraform/template/aws/cluster.tf.tpl | 15 +++ deploy/terraform/template/azure/.envrc.tpl | 1 + .../terraform/template/azure/cluster.tf.tpl | 17 +++ .../running-sifchain-validator-on-aks.md | 89 +++++++++++++ 26 files changed, 996 insertions(+), 2 deletions(-) create mode 100644 deploy/helm/sifnode/.helmignore create mode 100644 deploy/helm/sifnode/Chart.yaml create mode 100644 deploy/helm/sifnode/templates/_helpers.tpl create mode 100644 deploy/helm/sifnode/templates/serviceaccount.yaml create mode 100644 deploy/helm/sifnode/templates/sifnode-configmap.yml create mode 100644 deploy/helm/sifnode/templates/sifnode-deployment.yaml create mode 100644 deploy/helm/sifnode/templates/sifnode-pvc.yml create mode 100644 deploy/helm/sifnode/templates/sifnode-role.yml create mode 100644 deploy/helm/sifnode/templates/sifnode-rolebinding.yml create mode 100644 deploy/helm/sifnode/templates/sifnode-secret.yml create mode 100644 deploy/helm/sifnode/templates/sifnode-service.yaml create mode 100644 deploy/helm/sifnode/values.yaml create mode 100644 deploy/rake/cluster.rake create mode 100644 deploy/rake/helpers.rake create mode 100644 deploy/terraform/providers/aws/main.tf create mode 100644 deploy/terraform/providers/aws/outputs.tf create mode 100644 deploy/terraform/providers/aws/variables.tf create mode 100644 deploy/terraform/providers/azure/main.tf create mode 100644 deploy/terraform/providers/azure/outputs.tf create mode 100644 deploy/terraform/providers/azure/variables.tf create mode 100644 deploy/terraform/template/aws/.envrc.tpl create mode 100644 deploy/terraform/template/aws/cluster.tf.tpl create mode 100644 deploy/terraform/template/azure/.envrc.tpl create mode 100644 deploy/terraform/template/azure/cluster.tf.tpl create mode 100644 docs/tutorials/running-sifchain-validator-on-aks.md diff --git a/.gitignore b/.gitignore index 9c1a9f73b8..60b18328ff 100644 --- a/.gitignore +++ b/.gitignore @@ -23,11 +23,13 @@ relayerdb venv/ deploy - +!deploy/ +!deploy/** +deploy/networks smart-contracts/combined.log smart-contracts/yarn-error.log test/integration/output.json test/integration/sifchainrelayerdb/* *.log -dist \ No newline at end of file +dist diff --git a/deploy/helm/sifnode/.helmignore b/deploy/helm/sifnode/.helmignore new file mode 100644 index 0000000000..414bb6e8a6 --- /dev/null +++ b/deploy/helm/sifnode/.helmignore @@ -0,0 +1,18 @@ +# Patterns to ignore when building packages. +.DS_Store +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +*.swp +*.bak +*.tmp +*.orig +*~ +.project +.idea/ +*.tmproj +.vscode/ diff --git a/deploy/helm/sifnode/Chart.yaml b/deploy/helm/sifnode/Chart.yaml new file mode 100644 index 0000000000..cca2a8eb4a --- /dev/null +++ b/deploy/helm/sifnode/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: sifnode +description: Sifchain full node +type: application +version: 0.1.0 +appVersion: 1.16.0 diff --git a/deploy/helm/sifnode/templates/_helpers.tpl b/deploy/helm/sifnode/templates/_helpers.tpl new file mode 100644 index 0000000000..baaa6d4001 --- /dev/null +++ b/deploy/helm/sifnode/templates/_helpers.tpl @@ -0,0 +1,43 @@ +{{/* vim: set filetype=mustache: */}} +{{- define "sifnode.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "sifnode.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "sifnode.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "sifnode.labels" -}} +helm.sh/chart: {{ include "sifnode.chart" . }} +{{ include "sifnode.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "sifnode.selectorLabels" -}} +app.kubernetes.io/name: {{ include "sifnode.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "sifnode.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "sifnode.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/deploy/helm/sifnode/templates/serviceaccount.yaml b/deploy/helm/sifnode/templates/serviceaccount.yaml new file mode 100644 index 0000000000..e26804d9cf --- /dev/null +++ b/deploy/helm/sifnode/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "sifnode.serviceAccountName" . }} + labels: + {{- include "sifnode.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/deploy/helm/sifnode/templates/sifnode-configmap.yml b/deploy/helm/sifnode/templates/sifnode-configmap.yml new file mode 100644 index 0000000000..fdf50ca528 --- /dev/null +++ b/deploy/helm/sifnode/templates/sifnode-configmap.yml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "sifnode.fullname" . }}-scripts +data: + external-ip.sh: | + #!/bin/sh + + apk add --no-cache bind-tools >/dev/null 2>&1 || true + + SERVICE=$1 + CONFIGMAP=$2 + + external_ip="" + + while [ -z "$external_ip" ]; do + echo "Waiting for load balancer external endpoint..." + external_ip=$(kubectl get svc "$SERVICE" -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + if [ -z "$external_ip" ]; then + hostname=$(kubectl get svc "$SERVICE" -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') + [ ! -z "$hostname" ] && external_ip=$(dig +short "$hostname" | sort | head -1) + fi + [ -z "$external_ip" ] && sleep 10 + done + + kubectl create configmap "$CONFIGMAP" --from-literal=externalIP="$external_ip" --dry-run=client -o yaml | kubectl apply -f - diff --git a/deploy/helm/sifnode/templates/sifnode-deployment.yaml b/deploy/helm/sifnode/templates/sifnode-deployment.yaml new file mode 100644 index 0000000000..ce041cba83 --- /dev/null +++ b/deploy/helm/sifnode/templates/sifnode-deployment.yaml @@ -0,0 +1,111 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "sifnode.fullname" . }} + labels: + {{- include "sifnode.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "sifnode.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "sifnode.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "sifnode.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + - name: external-ip + image: alpine/k8s:1.18.2 + command: + - /bin/sh + - /scripts/external-ip.sh + - sifnode + - {{ printf "%s-external-ip" (include "sifnode.fullname" .) | quote }} + volumeMounts: + - name: scripts + mountPath: /scripts + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["/bin/sh"] + volumeMounts: + - name: data + mountPath: /root/ + {{- if .Values.sifnode.env.peerAddress }} + args: ["-c", "sifgen node create $CHAINNET $MONIKER \"$MNEMONIC\" $EXTERNAL_IP $PEER_ADDRESS $GENESIS_URL; sifnoded start --rpc.laddr tcp://0.0.0.0:26657"] + {{- else }} + args: ["-c", "sifgen node create $CHAINNET $MONIKER \"$MNEMONIC\" $EXTERNAL_IP; sifnoded start --rpc.laddr tcp://0.0.0.0:26657"] + {{- end }} + ports: + - name: p2p + containerPort: 26656 + protocol: TCP + - name: rpc + containerPort: 26657 + protocol: TCP + env: + - name: CHAINNET + value: {{ .Values.sifnode.env.chainnet | quote }} + - name: MONIKER + value: {{ .Values.sifnode.env.moniker | quote }} + - name: MNEMONIC + valueFrom: + secretKeyRef: + name: {{ include "sifnode.fullname" . }} + key: mnemonic + - name: EXTERNAL_IP + valueFrom: + configMapKeyRef: + name: {{ include "sifnode.fullname" . }}-external-ip + key: externalIP + - name: PEER_ADDRESS + value: {{ .Values.sifnode.env.peerAddress | quote }} + - name: GENESIS_URL + value: {{ .Values.sifnode.env.genesisURL | quote }} + livenessProbe: + tcpSocket: + port: 26657 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 26657 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + - name: data + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ template "sifnode.fullname" . }} + {{- else }} + emptyDir: {} + {{- end }} + - name: scripts + configMap: + name: {{ include "sifnode.fullname" . }}-scripts + defaultMode: 0777 + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/deploy/helm/sifnode/templates/sifnode-pvc.yml b/deploy/helm/sifnode/templates/sifnode-pvc.yml new file mode 100644 index 0000000000..dc1f4b3b80 --- /dev/null +++ b/deploy/helm/sifnode/templates/sifnode-pvc.yml @@ -0,0 +1,23 @@ +{{- if .Values.persistence.enabled }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "sifnode.fullname" . }} + labels: + {{- include "sifnode.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- if .Values.persistence.storageClass }} + {{- if (eq "-" .Values.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.persistence.storageClass }}" + {{- end }} + {{- else if eq .Values.global.cloudProvider "azure" }} + storageClassName: "managed-csi" + {{- end }} +{{- end -}} diff --git a/deploy/helm/sifnode/templates/sifnode-role.yml b/deploy/helm/sifnode/templates/sifnode-role.yml new file mode 100644 index 0000000000..34ca4915a9 --- /dev/null +++ b/deploy/helm/sifnode/templates/sifnode-role.yml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "sifnode.fullname" . }} + labels: +{{ include "sifnode.labels" . | nindent 4 }} +rules: +- apiGroups: [""] + resources: ["services", "configmaps"] + verbs: ["get", "watch", "list", "create", "update", "patch", "delete"] +{{- end -}} diff --git a/deploy/helm/sifnode/templates/sifnode-rolebinding.yml b/deploy/helm/sifnode/templates/sifnode-rolebinding.yml new file mode 100644 index 0000000000..d409b02ed1 --- /dev/null +++ b/deploy/helm/sifnode/templates/sifnode-rolebinding.yml @@ -0,0 +1,16 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "sifnode.fullname" . }} + labels: +{{ include "sifnode.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "sifnode.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ include "sifnode.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/deploy/helm/sifnode/templates/sifnode-secret.yml b/deploy/helm/sifnode/templates/sifnode-secret.yml new file mode 100644 index 0000000000..065c151dfb --- /dev/null +++ b/deploy/helm/sifnode/templates/sifnode-secret.yml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "sifnode.fullname" . }} +type: Opaque +data: + mnemonic: {{ .Values.sifnode.env.mnemonic | b64enc }} diff --git a/deploy/helm/sifnode/templates/sifnode-service.yaml b/deploy/helm/sifnode/templates/sifnode-service.yaml new file mode 100644 index 0000000000..fef165e680 --- /dev/null +++ b/deploy/helm/sifnode/templates/sifnode-service.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "sifnode.fullname" . }} + labels: + {{- include "sifnode.labels" . | nindent 4 }} + {{- $isAws := eq .Values.global.cloudProvider "aws" }} + {{- if or $isAws .Values.sifnode.service.annotations }} + annotations: + {{- if $isAws }} + service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true" + service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "tcp" + service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" + service.beta.kubernetes.io/aws-load-balancer-type: "nlb" + {{- end }} + {{- with .Values.sifnode.service.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +spec: + type: {{ .Values.sifnode.service.type }} + ports: + - port: 26656 + protocol: TCP + name: p2p + - port: 26657 + protocol: TCP + name: rpc + selector: + {{- include "sifnode.selectorLabels" . | nindent 4 }} diff --git a/deploy/helm/sifnode/values.yaml b/deploy/helm/sifnode/values.yaml new file mode 100644 index 0000000000..a7161d7e1b --- /dev/null +++ b/deploy/helm/sifnode/values.yaml @@ -0,0 +1,56 @@ +global: + cloudProvider: aws + +replicaCount: 1 + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} +podSecurityContext: {} +securityContext: {} + +image: + repository: sifchain/sifnoded + pullPolicy: Always + tag: "testnet" + +sifnode: + env: + chainnet: + moniker: + genesisURL: + peerAddress: + mnemonic: "" + service: + type: LoadBalancer + annotations: {} + +service: + type: LoadBalancer + port: 26656 + +persistence: + enabled: true + size: 500Gi + accessMode: ReadWriteOnce + storageClass: "" + +ingress: + enabled: false + annotations: {} + hosts: + - host: sifnode.local + paths: [] + tls: [] + +resources: {} +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/deploy/rake/cluster.rake b/deploy/rake/cluster.rake new file mode 100644 index 0000000000..1055687e1a --- /dev/null +++ b/deploy/rake/cluster.rake @@ -0,0 +1,119 @@ +require "fileutils" + +desc "management processes for the kube cluster and terraform commands" +namespace :cluster do + desc "Scaffold new cluster environment configuration" + task :scaffold, [:chainnet, :provider] do |_t, args| + check_args(args) + + FileUtils.mkdir_p("#{cwd}/../../.live") + FileUtils.mkdir_p(path(args)) + + template_root = "#{cwd}/../terraform/template/#{template_provider_id(args[:provider])}" + render_template( + "#{template_root}/cluster.tf.tpl", + "#{path(args)}/main.tf", + { chainnet: args[:chainnet] } + ) + + envrc_template = "#{template_root}/.envrc.tpl" + if File.exist?(envrc_template) + render_template( + envrc_template, + "#{path(args)}/.envrc", + { chainnet: args[:chainnet] } + ) + end + + Dir.chdir(path(args)) do + system("terraform init") or exit 1 + end + + puts "Cluster configuration scaffolding complete: #{path(args)}" + end + + desc "Deploy a new cluster" + task :deploy, [:chainnet, :provider] do |_t, args| + check_args(args) + puts "Deploy cluster config: #{path(args)}" + + Dir.chdir(path(args)) do + system("terraform apply -auto-approve") or exit 1 + end + + write_kubeconfig(args) + puts "Cluster #{path(args)} created successfully" + end + + desc "Destroy a cluster" + task :destroy, [:chainnet, :provider] do |_t, args| + check_args(args) + puts "Destroy running cluster: #{path(args)}" + + Dir.chdir(path(args)) do + system("terraform destroy") or exit 1 + end + + File.delete(kubeconfig(args)) if File.exist?(kubeconfig(args)) + puts "Cluster #{path(args)} destroyed successfully" + end + + desc "Manage sifnode deploy, upgrade, etc processes" + namespace :sifnode do + namespace :deploy do + desc "Deploy a single standalone sifnode on to your cluster" + task :standalone, [:chainnet, :provider, :namespace, :image, :image_tag, :moniker, :mnemonic] do |_t, args| + check_args(args) + + cmd = %Q{helm upgrade sifnode #{cwd}/../../deploy/helm/sifnode \ + --install -n #{ns(args)} --create-namespace \ + --set sifnode.env.chainnet=#{args[:chainnet]} \ + --set sifnode.env.moniker=#{args[:moniker]} \ + --set sifnode.env.mnemonic="#{args[:mnemonic]}" \ + #{common_helm_flags(args)} + } + + system({ "KUBECONFIG" => kubeconfig(args) }, cmd) or exit 1 + end + + desc "Deploy a single network-aware sifnode on to your cluster" + task :peer, [:chainnet, :provider, :namespace, :image, :image_tag, :moniker, :mnemonic, :peer_address, :genesis_url] do |_t, args| + check_args(args) + + cmd = %Q{helm upgrade sifnode #{cwd}/../../deploy/helm/sifnode \ + --install -n #{ns(args)} --create-namespace \ + --set sifnode.env.chainnet=#{args[:chainnet]} \ + --set sifnode.env.moniker=#{args[:moniker]} \ + --set sifnode.env.mnemonic="#{args[:mnemonic]}" \ + --set sifnode.env.peerAddress=#{args[:peer_address]} \ + --set sifnode.env.genesisURL=#{args[:genesis_url]} \ + #{common_helm_flags(args)} + } + + system({ "KUBECONFIG" => kubeconfig(args) }, cmd) or exit 1 + end + end + + desc "Destroy an existing namespace" + task :destroy, [:chainnet, :provider, :namespace, :skip_prompt] do |_t, args| + check_args(args) + are_you_sure(args) + cmd = "kubectl delete namespace #{ns(args)}" + system({ "KUBECONFIG" => kubeconfig(args) }, cmd) or exit 1 + end + end +end + +# +# Get the path to our terraform config based off the supplied rake args +# +def path(args) + "#{cwd}/../../.live/sifchain-#{provider_id(args[:provider])}-#{args[:chainnet]}" +end + +# +# Get the path to our kubeconfig based off the supplied rake args +# +def kubeconfig(args) + "#{path(args)}/kubeconfig_sifchain-#{provider_id(args[:provider])}-#{args[:chainnet]}" +end diff --git a/deploy/rake/helpers.rake b/deploy/rake/helpers.rake new file mode 100644 index 0000000000..cc8f28ea5f --- /dev/null +++ b/deploy/rake/helpers.rake @@ -0,0 +1,121 @@ +# +# Current working directory. +# +def cwd + File.dirname(__FILE__) +end + +# +# Normalize provider aliases to the canonical names we use in paths and docs. +# +def provider_id(provider) + case provider&.downcase + when "aws" + "aws" + when "az", "azure" + "azure" + else + provider + end +end + +# +# Terraform template folder name for the selected cloud. +# +def template_provider_id(provider) + provider_id(provider) +end + +# +# Check the supplied arguments +# +# @param args Arguments passed to rake +# +def check_args(args) + if args[:chainnet].nil? + puts "Please provide a chainnet argument, for example: testnet or mainnet" + exit 1 + end + + case provider_id(args[:provider]) + when "aws", "azure" + else + puts "Please provide a cloud provider. Supported values: aws, azure" + exit 1 + end +end + +# +# Generic prompt +# +# @param args Arguments passed to rake +# +def are_you_sure(args) + return unless args[:skip_prompt].nil? + + STDOUT.puts "Are you sure? (y/n)" + + begin + input = STDIN.gets.strip.downcase + end until %w[y n].include?(input) + + exit(0) if input != "y" +end + +# +# k8s namespace +# +# @param args Arguments passed to rake +# +def ns(args) + args[:namespace] ? args[:namespace].to_s : "sifnode" +end + +# +# Image tag +# +# @param args Arguments passed to rake +# +def image_tag(args) + args[:image_tag] ? args[:image_tag].to_s : "testnet" +end + +# +# Image repository +# +# @param args Arguments passed to rake +# +def image_repository(args) + args[:image] ? args[:image].to_s : "sifchain/sifnoded" +end + +# +# Helm values shared across deployment tasks. +# +def common_helm_flags(args) + [ + "--set global.cloudProvider=#{provider_id(args[:provider])}", + "--set image.tag=#{image_tag(args)}", + "--set image.repository=#{image_repository(args)}" + ].join(" ") +end + +# +# Render a gotpl template into a file. +# +def render_template(template_path, output_path, values) + set_args = values.map { |key, value| "--set #{key}=#{value}" }.join(" ") + system("go run github.com/belitre/gotpl #{template_path} #{set_args} > #{output_path}") or exit 1 +end + +# +# Capture terraform output and persist it as the kubeconfig used by later tasks. +# +def write_kubeconfig(args) + Dir.chdir(path(args)) do + kubeconfig_content = `terraform output -raw kubectl_config` + raise "terraform output -raw kubectl_config failed" unless $?.success? + + File.write(kubeconfig(args), kubeconfig_content) + end +end diff --git a/deploy/terraform/providers/aws/main.tf b/deploy/terraform/providers/aws/main.tf new file mode 100644 index 0000000000..e5afa82dde --- /dev/null +++ b/deploy/terraform/providers/aws/main.tf @@ -0,0 +1,65 @@ +terraform { + required_version = ">= 0.13" +} + +provider "aws" { + region = var.region + profile = var.profile +} + +data "aws_eks_cluster" "cluster" { + name = module.eks.cluster_id +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + + name = var.cluster_name + cidr = var.vpc_cidr + azs = [for az in var.az : format("%s%s", var.region, az)] + public_subnets = [cidrsubnet(var.vpc_cidr, 4, 1), cidrsubnet(var.vpc_cidr, 4, 2), cidrsubnet(var.vpc_cidr, 4, 3)] + + enable_dns_hostnames = true + enable_dns_support = true + + map_public_ip_on_launch = true + + tags = { + "kubernetes.io/cluster/${var.cluster_name}" = "shared" + } + + public_subnet_tags = { + "kubernetes.io/cluster/${var.cluster_name}" = "shared" + "kubernetes.io/role/elb" = "1" + } +} + +module "eks" { + source = "terraform-aws-modules/eks/aws" + cluster_name = var.cluster_name + subnets = module.vpc.public_subnets + vpc_id = module.vpc.vpc_id + tags = merge({ "Name" = var.cluster_name }, var.tags) + + node_groups_defaults = { + ami_type = var.ami_type + disk_size = var.disk_size + } + + node_groups = { + main = { + desired_capacity = var.desired_capacity + max_capacity = var.max_capacity + min_capacity = var.min_capacity + instance_type = var.instance_type + + k8s_labels = { + Environment = "${var.cluster_name}-${var.region}" + } + additional_tags = var.tags + } + } + + cluster_version = var.cluster_version + write_kubeconfig = true +} diff --git a/deploy/terraform/providers/aws/outputs.tf b/deploy/terraform/providers/aws/outputs.tf new file mode 100644 index 0000000000..244eeda6a8 --- /dev/null +++ b/deploy/terraform/providers/aws/outputs.tf @@ -0,0 +1,4 @@ +output "kubectl_config" { + description = "kubectl config" + value = module.eks.kubeconfig +} diff --git a/deploy/terraform/providers/aws/variables.tf b/deploy/terraform/providers/aws/variables.tf new file mode 100644 index 0000000000..4dde31ccfc --- /dev/null +++ b/deploy/terraform/providers/aws/variables.tf @@ -0,0 +1,63 @@ +variable "region" { + description = "AWS region" + type = string +} + +variable "az" { + description = "AWS availability zones" + default = ["a", "b", "c"] +} + +variable "vpc_cidr" { + description = "VPC cidr" + type = string + default = "10.0.0.0/16" +} + +variable "cluster_version" { + description = "EKS cluster version" + type = string + default = "1.18" +} + +variable "cluster_name" { + description = "EKS cluster name" + type = string +} + +variable "tags" { + description = "Tags" + type = map(string) +} + +variable "desired_capacity" { + description = "Desired kubernetes nodes per cluster" + default = 1 +} + +variable "max_capacity" { + description = "Maximum kubernetes nodes per cluster" + default = 3 +} + +variable "min_capacity" { + description = "Minimum kubernetes nodes per cluster" + default = 1 +} + +variable "instance_type" { + default = "t2.medium" +} + +variable "ami_type" { + default = "AL2_x86_64" +} + +variable "disk_size" { + default = 100 +} + +variable "profile" { + description = "AWS profile settings" + default = "default" +} diff --git a/deploy/terraform/providers/azure/main.tf b/deploy/terraform/providers/azure/main.tf new file mode 100644 index 0000000000..8bbfc5830c --- /dev/null +++ b/deploy/terraform/providers/azure/main.tf @@ -0,0 +1,63 @@ +terraform { + required_version = ">= 0.13" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.117" + } + } +} + +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "cluster" { + name = var.resource_group_name + location = var.location + tags = var.tags +} + +resource "azurerm_virtual_network" "cluster" { + name = "${var.cluster_name}-vnet" + location = azurerm_resource_group.cluster.location + resource_group_name = azurerm_resource_group.cluster.name + address_space = [var.vnet_cidr] + tags = var.tags +} + +resource "azurerm_subnet" "cluster" { + name = "${var.cluster_name}-subnet" + resource_group_name = azurerm_resource_group.cluster.name + virtual_network_name = azurerm_virtual_network.cluster.name + address_prefixes = [var.subnet_cidr] +} + +resource "azurerm_kubernetes_cluster" "cluster" { + name = var.cluster_name + location = azurerm_resource_group.cluster.location + resource_group_name = azurerm_resource_group.cluster.name + dns_prefix = var.dns_prefix + kubernetes_version = var.kubernetes_version + tags = var.tags + role_based_access_control_enabled = true + + default_node_pool { + name = "default" + vm_size = var.vm_size + node_count = var.node_count + os_disk_size_gb = var.os_disk_size_gb + vnet_subnet_id = azurerm_subnet.cluster.id + enable_auto_scaling = false + } + + identity { + type = "SystemAssigned" + } + + network_profile { + network_plugin = "azure" + load_balancer_sku = "standard" + } +} diff --git a/deploy/terraform/providers/azure/outputs.tf b/deploy/terraform/providers/azure/outputs.tf new file mode 100644 index 0000000000..72381ebbc8 --- /dev/null +++ b/deploy/terraform/providers/azure/outputs.tf @@ -0,0 +1,13 @@ +output "kubectl_config" { + description = "kubectl config" + value = azurerm_kubernetes_cluster.cluster.kube_config_raw + sensitive = true +} + +output "resource_group_name" { + value = azurerm_resource_group.cluster.name +} + +output "cluster_name" { + value = azurerm_kubernetes_cluster.cluster.name +} diff --git a/deploy/terraform/providers/azure/variables.tf b/deploy/terraform/providers/azure/variables.tf new file mode 100644 index 0000000000..39eb4f8752 --- /dev/null +++ b/deploy/terraform/providers/azure/variables.tf @@ -0,0 +1,61 @@ +variable "location" { + description = "Azure region" + type = string +} + +variable "resource_group_name" { + description = "Resource group for the AKS cluster" + type = string +} + +variable "cluster_name" { + description = "AKS cluster name" + type = string +} + +variable "dns_prefix" { + description = "AKS DNS prefix" + type = string +} + +variable "kubernetes_version" { + description = "AKS kubernetes version" + type = string + default = "1.35.5" +} + +variable "node_count" { + description = "Number of nodes in the default pool" + type = number + default = 1 +} + +variable "vm_size" { + description = "VM size for AKS worker nodes" + type = string + default = "Standard_B2s_v2" +} + +variable "os_disk_size_gb" { + description = "OS disk size for worker nodes" + type = number + default = 128 +} + +variable "vnet_cidr" { + description = "Virtual network CIDR" + type = string + default = "10.20.0.0/16" +} + +variable "subnet_cidr" { + description = "Cluster subnet CIDR" + type = string + default = "10.20.1.0/24" +} + +variable "tags" { + description = "Tags" + type = map(string) + default = {} +} diff --git a/deploy/terraform/template/aws/.envrc.tpl b/deploy/terraform/template/aws/.envrc.tpl new file mode 100644 index 0000000000..df061279fb --- /dev/null +++ b/deploy/terraform/template/aws/.envrc.tpl @@ -0,0 +1 @@ +export KUBECONFIG=$(pwd)/kubeconfig_sifchain-aws-{{.chainnet}} diff --git a/deploy/terraform/template/aws/cluster.tf.tpl b/deploy/terraform/template/aws/cluster.tf.tpl new file mode 100644 index 0000000000..cafddb25c2 --- /dev/null +++ b/deploy/terraform/template/aws/cluster.tf.tpl @@ -0,0 +1,15 @@ +module sifchain { + source = "../../deploy/terraform/providers/aws" + region = "us-west-2" + cluster_name = "sifchain-aws-{{.chainnet}}" + tags = { + Terraform = "true" + Project = "sifchain" + Chainnet = "{{.chainnet}}" + } +} + +output "kubectl_config" { + value = module.sifchain.kubectl_config + sensitive = true +} diff --git a/deploy/terraform/template/azure/.envrc.tpl b/deploy/terraform/template/azure/.envrc.tpl new file mode 100644 index 0000000000..799a6e3fe7 --- /dev/null +++ b/deploy/terraform/template/azure/.envrc.tpl @@ -0,0 +1 @@ +export KUBECONFIG=$(pwd)/kubeconfig_sifchain-azure-{{.chainnet}} diff --git a/deploy/terraform/template/azure/cluster.tf.tpl b/deploy/terraform/template/azure/cluster.tf.tpl new file mode 100644 index 0000000000..be3ab461eb --- /dev/null +++ b/deploy/terraform/template/azure/cluster.tf.tpl @@ -0,0 +1,17 @@ +module sifchain { + source = "../../deploy/terraform/providers/azure" + location = "southeastasia" + resource_group_name = "sifchain-azure-{{.chainnet}}" + cluster_name = "sifchain-azure-{{.chainnet}}" + dns_prefix = "sifchain-{{.chainnet}}" + tags = { + Terraform = "true" + Project = "sifchain" + Chainnet = "{{.chainnet}}" + } +} + +output "kubectl_config" { + value = module.sifchain.kubectl_config + sensitive = true +} diff --git a/docs/tutorials/running-sifchain-validator-on-aks.md b/docs/tutorials/running-sifchain-validator-on-aks.md new file mode 100644 index 0000000000..dccf59c7f2 --- /dev/null +++ b/docs/tutorials/running-sifchain-validator-on-aks.md @@ -0,0 +1,89 @@ +# Running a Sifchain Validator on AKS + +This repository now includes Azure AKS deployment scaffolding that matches the older AWS/EKS workflow. + +## Prerequisites + +- Azure CLI installed +- Terraform installed +- Helm 3 installed +- `kubectl` installed +- Ruby installed +- Go installed + +## 1. Sign in to Azure + +```bash +az login +az account set --subscription "" +``` + +The Terraform under `deploy/terraform/providers/azure` uses the standard Azure CLI authentication flow, so `az login` is enough for local testing. + +## 2. Scaffold the AKS environment + +From the repo root: + +```bash +rake cluster:scaffold[testnet,azure] +``` + +This creates `.live/sifchain-azure-testnet/main.tf`. Before deploying, review it and adjust any of these defaults if needed: + +- `location` +- `resource_group_name` +- `cluster_name` +- node sizing or Kubernetes version via the module inputs + +The scaffold currently defaults to `southeastasia`, which is a safer starting point for restricted student subscriptions than `eastus`. If your subscription is limited to a different region set, change `location` before applying. + +## 3. Create the AKS cluster + +```bash +rake cluster:deploy[testnet,azure] +``` + +After Terraform finishes, the task writes the kubeconfig file to: + +```text +.live/sifchain-azure-testnet/kubeconfig_sifchain-azure-testnet +``` + +## 4. Deploy sifnode + +Standalone validator-style deployment: + +```bash +rake cluster:sifnode:deploy:standalone[testnet,azure,sifnode,sifchain/sifnoded,testnet,my-validator,"your mnemonic here"] +``` + +If you want the node to join an existing network with an explicit peer and genesis: + +```bash +rake cluster:sifnode:deploy:peer[testnet,azure,sifnode,sifchain/sifnoded,testnet,my-validator,"your mnemonic here",,] +``` + +## 5. Verify the deployment + +Point `kubectl` at the generated kubeconfig: + +```bash +kubectl --kubeconfig .live/sifchain-azure-testnet/kubeconfig_sifchain-azure-testnet get pods -n sifnode +kubectl --kubeconfig .live/sifchain-azure-testnet/kubeconfig_sifchain-azure-testnet get svc -n sifnode +``` + +The chart waits for the AKS load balancer address and uses that external address when generating the node config. On Azure, the persistent volume claim defaults to the `managed-csi` storage class. + +## 6. Clean up + +Delete the namespace only: + +```bash +rake cluster:sifnode:destroy[testnet,azure,sifnode,skip] +``` + +Destroy the entire AKS cluster: + +```bash +rake cluster:destroy[testnet,azure] +```