In this tutorial I will demonstrate how to run a Ethereum 2 validator within a kubernetes cluster. We use Prysm as Eth2 and openethereum as Eth1 client.
The most obvious things first. Although openethereum and Prysm offer a Docker image, both of them will tell you to use the native client. One reason could be the focus on the main goal: providing a stable and functional client. A second once could be the reason that you can’t use more than one client at a time. In fact, if you try to run two clients together it can damage the network and cost you Ether. This means that one of the big advantages of containers, the elasticity, is not applicable here. But if you want to do it just because you can or you enjoy the self healing of damaged pods while you drive around with your lambo you earned from staking Ether your are good to go.
I don’t want to give you a large introduction to the new Ethereum ecosystem, you can read all relevant infos on their website. However there are a few things you need to know before we start.
This tutorial was done within the Ethereum testnet. To be able to setup a validator you need 32 Ether. First you need a wallet which is able to connect to the testnet. I would recoment MetaMask. Make sure to select the gorli network. Afterwards you can get Ether for free from the görli foucet. Those Ether have no value, but can be used for testing.
Its woth mentioning that Ethereum 2 isn’t a new system. Its more an overlay to Ethereum 1. The old Ethereum 1 blockchain will be a part of Ethereum 2. This means we have to install not only the Eth2 beacon node and the ETH 2 validator but also the traditional Eth1 client. At the end our cluster will look like this:

Setup the environment
If not already done, we have to install kubernetes. I used microk8s, but minikube is also a feasible choice. I used linux as the operating system. You can also use Windows, but I wan’t be able to support you with your setup. Installing kubernetes is often tricky but there are many tutorials out there that will help you.
We also need some permanent storage for our clients. The chain synchronization can take several days. You don’t want to start all over again if you restart your deployment. To store the chain information we create a simple folder in my home directory.
mkdir ~/.local/share/k8s/eth/testnet/openethereum
mkdir ~/.local/share/k8s/eth/testnet/prysm/validator
mkdir ~/.local/share/k8s/eth/testnet/prysm/beacon
Installing the Ethereum 1 client
As I already mentioned, we will use openethereum as our Eth1 client. Feel free to use another client if you want to.
apiVersion: apps/v1
kind: Deployment
metadata:
name: openethereum-deployment
labels:
app: openethereum
spec:
selector:
matchLabels:
app: openethereum
replicas: 1
template:
metadata:
labels:
app: openethereum
spec:
containers:
- name: openethereum
image: openethereum/openethereum:latest
volumeMounts:
- name: openethereum
mountPath: /home/openethereum/.local/share/openethereum/
ports:
- containerPort: 30303
protocol: UDP
- containerPort: 30303
protocol: TCP
- containerPort: 8545
protocol: TCP
- containerPort: 8546
protocol: TCP
args:
- --unsafe-expose
#- --nat
#- extip:127.0.0.1
- --base-path
- /home/openethereum/.local/share/openethereum
- --chain
- goerli
volumes:
- name: openethereum
hostPath:
path: /home/<YOUR_HOME_FOLDER>/.local/share/k8s/eth/testnet/openethereum
---
apiVersion: v1
kind: Service
metadata:
name: openethereum-service
spec:
#type: NodePort
selector:
app: openethereum
ports:
- protocol: TCP
name: eth-tcp
port: 30303
targetPort: 30303
#nodePort: 30303
- protocol: UDP
name: eth-udp
port: 30303
targetPort: 30303
#nodePort: 30303
- protocol: TCP
name: rpc
port: 8545
targetPort: 8545
- protocol: TCP
name: websocket
port: 8546
targetPort: 8546
First, copy the code listing and save it as openethereum.yaml. Make sure to replace <YOUR_HOME_FOLDER> with your user or the entire path with the path you want the files to be stored.
You can execute the deployment with
kubectl apply -y openethereum.yaml
This will pull the docker image and create a deployment. It will also create a service to expose the needed ports. The client will be executed with the parameters: --unsafe-expose --base-path /home/openethereum/.local/share/openethereum --chain goerli.
--unsafe-expose is needed because every pod will have an own network endpoint within the virtual network of kubernetes. Usually you shouln’t use this option if you run the client directly on your local machine because all clients run on the same host and you don’t want to expose the management port 8545 to another host. But since we are using kubernetes this isn’t a problem because the network will be secured by other methods.
Be carefull to connect to the testnet by using --chain goerli. If you want to connect to the mainnet later just remove those lines.
Make sure everything went well by listing your active pods.
kubectl get pod | grep openethereum
This will return a pod name hopefully with the status Running. Now we have to wait for the synchronization. This will take a couple of hours, maybe days. You can check the current state or errors by examining the log.
kubectl logs <YOUR_POD_NAME>
As long as it says something like syncing we have to wait. It will show you the current block it is working on. You can estimate the remaining time by checking out the latest block number on Etherscan for görli, your current block and the blocks processed every second by your client.
After syncing is done the processor activity of your machine should drop to normal. Remember; we don’t want to mine ETH, we just need to examine the Eth1 chain for our validator. One of the big advantages of proof-of-stake.
During the first sync openethereum warps with snapshots to the nearest block that’s a divider by 5000 and starts the normal syncing process. In the background its loads the missed acient blocks. Unfortunatelly we can’t start the beacon chain until everything is fully synced because the beacon node needs to validate all genesis validators for the genesis block. The first validators where created about a month before the genesis block. So all blocks starting from November 2020 are needed.
Installing the beacon node
The next thing would be the beacon node. The beacon node is similar to the Ethereum 1 client. It also examines the blockchain. In this case the beacon chain. Because the old Ethereum chain is still a part of Ethereum 2 the beacon node has also to validate the old chain. This is done via a RPC connection to the Ethereum 1 client. To create a deployment for the beacon node we create a file called prysmbeacon.yaml.
apiVersion: apps/v1
kind: Deployment
metadata:
name: prysmbeacon-deployment
labels:
app: prysmbeacon
spec:
selector:
matchLabels:
app: prysmbeacon
strategy:
type: Recreate
replicas: 1
template:
metadata:
labels:
app: prysmbeacon
spec:
containers:
- name: prysmbeacon
image: gcr.io/prysmaticlabs/prysm/beacon-chain:stable
volumeMounts:
- name: prysmbeacon
mountPath: /data
ports:
- containerPort: 12000
protocol: UDP
- containerPort: 13000
protocol: TCP
- containerPort: 4000
protocol: TCP
args:
- --accept-terms-of-use
- --datadir=/data
- --rpc-host=0.0.0.0
- --monitoring-host=0.0.0.0
#- --p2p-host-ip=127.0.0.1
- --http-web3provider=http://openethereum-service:8545
- --pyrmont
volumes:
- name: prysmbeacon
hostPath:
path: /home/<YOUR_HOME_FOLDER>/.local/share/k8s/eth/testnet/prysm/beacon
---
apiVersion: v1
kind: Service
metadata:
name: prysmbeacon-service
spec:
#type: NodePort
selector:
app: prysmbeacon
ports:
- protocol: TCP
name: beacon-tcp
port: 4000
targetPort: 4000
- protocol: TCP
name: beacon2-tcp
port: 13000
targetPort: 13000
#nodePort: 13000
- protocol: TCP
name: beacon-udp
port: 12000
targetPort: 12000
#nodePort: 12000
Deploy the beacon node the same way we did with the Ethereum 1 client. Don’t forget to replace <YOUR_HOME_FOLDER> with your actual home folder.
kubectl apply -f prysmvalidator.yaml
The interesting arguments are --http-web3provider=http://openethereum-service:8545 which is the connection to the Ethereum 1 chain and --pyrmont which ensures you are working on the testnet. --accept-terms-of-use is necessary because you don’t have an interactive screen.
Create an Ethereum 2 validator key
Go to the Pyrmont Launch Pad and follow each step. Select openethereum and Prysm as clients. After everything is done you should have uploaded your deposit-data to the website, send 32 Ether to the burning contract and have received a validator keyfile.
Import the validator keystore
With the next step we will import the validator key into our validator directory. Don’t even think about using the docker client for this task. The cli is much faster and easier to use.
First we have to download it.
curl https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh --output prysm.sh && chmod +x prysm.sh
After that we can use it to import our validator key.
./prysm.sh validator accounts import --keys-dir=<VALIDATOR_KEY_FOLDER> --pyrmont
We have to enter a new password for the wallet and also the one you used for generating the validator key. Copy the keystore into your share folder.
~/.local/share/k8s/prysm/validator/wallet/direct/accounts/all-accounts.keystore.json
We will import the keystore later within the validator. To keep your password save we create a kubernetes secret. Therefore we have to encrypt it as base64.
echo -n "my wallet password" | base64
The base64 encryption is not used for security but to avoid problems with the encoding of special characters when importing it into the kubernetes secret.
apiVersion: v1 kind: Secret metadata: name: prysmvalidator-wallet-secret data: wallet-password: <REPLACE_ME>
Replace the secret with your base64 string and save the code listing as prysmvalidator-wallet-secret.yaml. Execute it with kubectl.
kubectl apply -f prysmvalidator-wallet-secret.yaml
You can delete the file afterwards to keep your password save.
Now we have to wait. The validator deposit will take roughly 15 hours. We also have to wait for the beacon chain to finish syncing bevor we can start the validator.
Installing the validator
The last part we need to do is to install the validator. First we create a new file location. Ensure you have the following path ready.
mkdir ~/.local/share/k8s/eth/testnet/prysm/validator/db
apiVersion: apps/v1
kind: Deployment
metadata:
name: prysmvalidator-deployment
labels:
app: prysmvalidator
spec:
selector:
matchLabels:
app: prysmvalidator
strategy:
type: Recreate
replicas: 1
template:
metadata:
labels:
app: prysmvalidator
spec:
containers:
- name: prysmvalidator
image: gcr.io/prysmaticlabs/prysm/validator:stable
volumeMounts:
- name: prysmvalidator
mountPath: /data
- name: prysmvalidator-wallet-secret
mountPath: /var/wallet-secret
readOnly: true
args:
- --accept-terms-of-use
- --beacon-rpc-provider=prysmbeacon-service:4000
- --wallet-dir=/data/wallet
- --datadir=/data/db
- --wallet-password-file=/var/wallet-secret/wallet-password
- --pyrmont
volumes:
- name: prysmvalidator
hostPath:
path: /home/<YOUR_HOME_FOLDER>/.local/share/k8s/prysm/validator
- name: prysmvalidator-wallet-secret
secret:
secretName: prysmvalidator-wallet-secret
Again, we have to replace <YOUR_HOME_FOLDER>. Everything else should be easy to unserstand except for the secret. We load the secret as volume prysmvalidator-wallet-secret into the path /var/wallet-secret which creates a password file. With the parameter --wallet-password-file we tell the validator to read that file as keystore password.
To see if everything is working you should check out the logs of your pod. You can also Monitor your validator at sites like beaconcha.in. Look for your validator public key in the output and search for it on the validator overview.
kubectl logs prysmvalidator-deployment-<YOUR_POD_ID> | grep pubKey
As soon as the validator is approved it should start generating Ether.
Open discovery ports
This step is not necessary but will help other clients to find you faster, by letting them establish a connection to your host. You may have noticed, that I commented out some nodePorts for the services. You can uncomment them and make them available outside of the kubernetes virtual network. Keep in mind that your node may not have all ports available. In this case simply use other ports. You can find the necessary parameters in the documentation from openethereum or prysm. Don’t route the RPC Ports to the outside world! This could be dangerous. Only route the discovery ports. If you use a router you may also have to forward those ports to your machine.
Because our pods run within a virtual network they don’t know your real ip address. You have to set it manually. You can do this for the openethereum client with the parameter --nat. The beacon chain client can be configured with --p2p-host-ip. Here you can also use --p2p-host-dns if you use a dynamic dns service because of changing ip addresses.
Safe disc space
If you want to safe some disc space you can disable the syncing process of the old blocks for the beacon chain with --disable-sync. With this parameter the beacon chain skip the initial sync and start normal syncing right away. Make sure to do your deposit after that.
Unfortunately the ethereum 1 chain needs a lot of space. You need at least a 1TB SSD drive to be able to create a Eth1 client supported beacon chain. Because the beacon chain needs some ancient blocks for the genesis validation it is not possible to deactivate the genesis blocks on openethereum.
One trick would be to do the initial sync with an public chain provider like Alchemy . They offer free quotas that should be enough for the initial syncing. After that you could return to your own openethereum client with no ancient blocks. To do so you have to add the parameter --no-ancient-blocks. But be warned. This is absolutely not recommended.