Skip to main content

Deploy a Besu public node using Kubernetes

You can use a cloud provider such as Amazon Elastic Kubernetes Service (EKS) or Azure Kubernetes Service (AKS) to deploy a Besu public node

This tutorial walks you through adding an extra node group to your Besu pod.

AWS EKS

Prerequisites

Set up a Kubernetes cluster using a managed Kubernetes service such as Amazon EKS.

Steps

1. Create a security group for discovery

Create a security group in your VPC that allows traffic from anywhere on ports 30303 and 9000 (or equivalent ports that you are using for discovery).

Outbound rules
TypeProtocolPort rangeDestination
All trafficAllAll0.0.0.0/0
All trafficAllAll::/0

Inbound rules

TypeProtocolPort rangeDestinationDescription
Custom UDPUDP90000.0.0.0/0CL client
Custom TCPTCP90000.0.0.0/0CL client
Custom UDPUDP303030.0.0.0/0EL client
Custom TCPTCP303030.0.0.0/0EL client
important

The key here is to allow traffic on both TCP and UDP for the consensus layer client and the execution layer client.

2. Add a node group to your cluster

In your VPC settings, enable Auto-assign public IPv4 address on the public subnets on which you spin up your nodes.

This allows you to isolate your Besu node on a public subnet and separate it from the other apps and node groups you might have running. If you are using EKSCTL, add the following snippet to your setup:

managedNodeGroups:
- name: ng-ethereum
instanceType: m6a.xlarge
desiredCapacity: 1 # Increase this capacity if you need more nodes.

subnets:
- public-subnet-id1
- public-subnet-id2
- public-subnet-id3
labels: { "ng": "ethereum" }
securityGroups:
attachIDs: ["sg-1234..."] # The ID of the security group from the previous step.
iam:
withAddonPolicies:
ebs: true
# efs: true
taints:
- key: ethereum
value: "true"
effect: NoSchedule
- key: ethereum
value: "true"
effect: NoExecute

If you are using Terraform, use something like the following for your new node pool:

    ng-ethereum = {
desired_size = 1
subnet_ids = module.vpc.public_subnets # Only public subnets here.
vpc_security_group_ids = [ sg-1234 ] # The ID of the security group from the previous step.
instance_types = ["m6a.xlarge"]
iam_role_name = "${local.name}-eks-ng-ethereum-role"
taints = [
{
key = "ethereum"
value = "true"
effect = "NO_SCHEDULE"
},
{
key = "ethereum"
value = "true"
effect = "NO_EXECUTE"
}
]
labels = {
workloadType = "ethereum"
}
...

3. Install the EBS or EFS drivers

We recommend using EBS or NvME storage for your chain data. For most cases, the EBS drivers or EFS drivers are sufficient. However, if you are using instance stores, use the Local Storage Static Provisioner instead.

4. Set up the pod

Now that the infrastructure is set up, use hostNetworking to bind your pod to the host and use the host node's public IP for your Besu node.

First, add the following snippet to your StatefulSet:

template:
metadata:
labels:
...
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
affinity: ...

Next, add an init container and a shared volume to store the public IP. The init container init runs and gets the public IP of the host using the AWS metadata service and saves it to a local shared volume besu-pip (between the init container and the Besu pod).

template:
metadata:
labels:
...
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
affinity: ...
initContainers:
- name: init
image: alpine/curl:8.5.0
volumeMounts:
- name: pip
mountPath: /pip
- name: shared-jwt
mountPath: /jwt
- name: besu-data
mountPath: /data
securityContext:
runAsUser: 0
command:
- /bin/bash
- -xec
- |
# Get the existing public IP to associate with.
PUBLIC_IP_TO_ASSOCIATE=$(curl http://ifconfig.me/ip)
# Store the public IP in a local file to be used by the container.
echo -ne "$PUBLIC_IP_TO_ASSOCIATE" > /pip/ip

# Create the JWT key.
openssl rand -hex 32 | tr -d "\n" > /jwt/jwtSecret.hex

# Update permissions on the data volume (if needed).
chown -R 1000:1000 /data

containers:
...

volumes:
- name: pip
emptyDir: {}
- name: jwt
emptyDir: {}
- name: besu-data
persistentVolumeClaim:
claimName: besu-pvc
- name: teku-data
persistentVolumeClaim:
claimName: teku-pvc

When you start Besu up in the pod, use the text file in pip as your p2p-host, which allows traffic in and out as normal.

template:
metadata:
labels:
...
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
affinity: ...
initContainers:
- name: init
image: alpine/curl:8.5.0
volumeMounts:
- name: pip
mountPath: /pip
- name: shared-jwt
mountPath: /jwt
- name: besu-data
mountPath: /data
securityContext:
runAsUser: 0
command:
- /bin/bash
- -xec
- |
# Get the existing public IP to associate with.
PUBLIC_IP_TO_ASSOCIATE=$(curl http://ifconfig.me/ip)
# Store the public IP in a local file to be used by the container.
echo -ne "$PUBLIC_IP_TO_ASSOCIATE" > /pip/ip

# Create the JWT key.
openssl rand -hex 32 | tr -d "\n" > /jwt/jwtSecret.hex

# Update permissions on the data volume (if needed).
chown -R 1000:1000 /data

containers:
- name: besu
image: hyperledger/besu:latest
volumeMounts:
- name: pip
mountPath: /pip
readOnly: true
- name: shared-jwt
mountPath: /jwt
- name: besu-data
mountPath: {{ .Values.settings.dataPath }}
ports:
- name: elc-rpc
containerPort: 8545
protocol: TCP
- name: elc-ws
containerPort: 8546
protocol: TCP
- name: elc-rlpx
containerPort: 30303
protocol: TCP
- name: elc-discovery
containerPort: 30303
protocol: UDP
- name: elc-metrics
containerPort: 8545
protocol: TCP
- name: elc-engine
containerPort: 8551
protocol: TCP
command:
- /bin/sh
- -c
args:
- |
pip=$(cat /pip/ip)
/opt/besu/bin/besu \
--p2p-host=${pip} \
...

- name: teku
image: consensys/teku:develop
...

volumes:
- name: pip
emptyDir: {}
- name: jwt
emptyDir: {}
- name: besu-data
persistentVolumeClaim:
claimName: besu-pvc
- name: teku-data
persistentVolumeClaim:
claimName: teku-pvc

Azure AKS

The process for Azure is much the same as that of AWS with a couple of differences.

1. Create a Network Security Group (NSG)

Create a NSG with ports 30303 and 9000 (or equivalent) open for TCP and UDP. Bind this NSG with the subnet you've designated for your Ethereum nodes to ensure that nodes initiated within this subnet will automatically inherit these security rules.

2. Add a node pool to your cluster

In Azure all machines get allocated a public IP by default but you need to turn this on for your new node pool.

If you are using Terraform, use something like the following for your new node pool:

  node_pools = {
...
ethereum = {
name = "ethereum"
vm_size = "Standard_D8as_v5"
vnet_subnet_id = lookup(module.vnet.vnet_subnets_name_id, "subnet-....") # The ID of the security group from the previous step.
os_disk_size_gb = 100
min_count = 1
max_count = 10
enable_auto_scaling = true
enable_node_public_ip = true # This flag lets every node keep its public ip
enable_host_encryption = true
node_taints = ["ethereum=true:NoSchedule", "ethereum=true:NoExecute"]
node_labels = {
"workloadType" = "ethereum"
}
}

...
}

3. Use Azure StorageClasses to suit your needs

We recommend using either Azure Disk or Azure Files to store your chain data using the CSI storage drivers. If you are using a Terraform to provision your cluster e.g. terraform-azurerm-aks the CSI drivers are provisioned automatically for you.