Deploy in the Cloud¶
Run Ella Core in the cloud while your radios stay on site. Deploy a three-node cluster across three zones and connect a radio to it over IPsec.
Note
This guide uses cloud instances. Ella Core can also run on managed Kubernetes, such as GKE or EKS.
Prerequisites¶
- A radio that supports IPsec and several MME or AMF addresses. Without IPsec on the radio, use an IPsec router at the site.
- A Google Cloud project and the
gcloudCLI.
The examples use 203.0.113.10 for the site's public address, 192.168.1.50 for the radio, and 10.128.0.0/20 for the default subnet in us-central1.
- An AWS account and the
awsCLI.
The examples use 203.0.113.10 for the site's public address, 192.168.1.50 for the radio, and the default VPC (172.31.0.0/16) in us-east-1.
1. Prepare the project¶
Set the project and region:
gcloud config set project <project>
R="--region us-central1"
Allow the radio to reach Ella Core:
gcloud compute firewall-rules create ella-core-allow-radio --network default \
--allow sctp:36412,sctp:38412,udp:2152,icmp \
--source-ranges 192.168.1.50/32 --target-tags ella-core
Allow the nodes to reach each other:
gcloud compute firewall-rules create ella-core-allow-cluster --network default \
--allow tcp:7000 --source-tags ella-core --target-tags ella-core
Allow Identity-Aware Proxy (IAP) to reach SSH and the UI:
gcloud compute firewall-rules create ella-core-allow-iap --network default \
--allow tcp:22,tcp:5002 --source-ranges 35.235.240.0/20 --target-tags ella-core
Give the instances internet access:
gcloud compute routers create ella-nat-router --network default $R
gcloud compute routers nats create ella-nat --router ella-nat-router $R \
--auto-allocate-nat-external-ips --nat-all-subnet-ip-ranges
Set the region and find the default VPC:
export AWS_REGION=us-east-1
VPC=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true --query 'Vpcs[0].VpcId' --output text)
Allow the radio to reach Ella Core, and the nodes to reach each other:
SG=$(aws ec2 create-security-group --group-name ella-core --description "Ella Core" --vpc-id $VPC --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id $SG --ip-permissions \
'IpProtocol=132,IpRanges=[{CidrIp=192.168.1.50/32}]' \
'IpProtocol=udp,FromPort=2152,ToPort=2152,IpRanges=[{CidrIp=192.168.1.50/32}]' \
'IpProtocol=icmp,FromPort=-1,ToPort=-1,IpRanges=[{CidrIp=192.168.1.50/32}]' \
"IpProtocol=tcp,FromPort=7000,ToPort=7000,UserIdGroupPairs=[{GroupId=$SG}]"
Allow Session Manager to reach the instances:
aws iam create-role --role-name ella-core-ssm --assume-role-policy-document \
'{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name ella-core-ssm --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam create-instance-profile --instance-profile-name ella-core-ssm
aws iam add-role-to-instance-profile --instance-profile-name ella-core-ssm --role-name ella-core-ssm
2. Create the VPN gateway¶
Create a Classic VPN gateway:
gcloud compute addresses create radio-vpn-ip $R
GW=$(gcloud compute addresses describe radio-vpn-ip $R --format='value(address)')
echo $GW
gcloud compute target-vpn-gateways create radio-vpn --network default $R
gcloud compute forwarding-rules create radio-vpn-esp $R --address $GW --target-vpn-gateway radio-vpn --ip-protocol ESP
gcloud compute forwarding-rules create radio-vpn-udp500 $R --address $GW --target-vpn-gateway radio-vpn --ip-protocol UDP --ports 500
gcloud compute forwarding-rules create radio-vpn-udp4500 $R --address $GW --target-vpn-gateway radio-vpn --ip-protocol UDP --ports 4500
Create a pre-shared key, the tunnel, and a route to the radio:
head -c 32 /dev/urandom | base64 | tr -d '/+=' | head -c 32 > radio-vpn.psk
gcloud compute vpn-tunnels create radio-tunnel $R --target-vpn-gateway radio-vpn \
--peer-address 203.0.113.10 --ike-version 2 --shared-secret "$(cat radio-vpn.psk)" \
--local-traffic-selector 10.128.0.0/20 --remote-traffic-selector 192.168.1.50/32
gcloud compute routes create radio-route --network default --destination-range 192.168.1.50/32 \
--next-hop-vpn-tunnel radio-tunnel --next-hop-vpn-tunnel-region us-central1
Create a virtual private gateway and attach it to the VPC:
VGW=$(aws ec2 create-vpn-gateway --type ipsec.1 --query VpnGateway.VpnGatewayId --output text)
aws ec2 attach-vpn-gateway --vpn-gateway-id $VGW --vpc-id $VPC
until [ "$(aws ec2 describe-vpn-gateways --vpn-gateway-ids $VGW --query 'VpnGateways[0].VpcAttachments[0].State' --output text)" = attached ]; do sleep 5; done
RT=$(aws ec2 describe-route-tables --filters Name=vpc-id,Values=$VPC Name=association.main,Values=true --query 'RouteTables[0].RouteTableId' --output text)
aws ec2 enable-vgw-route-propagation --route-table-id $RT --gateway-id $VGW
Create a customer gateway for the site, a pre-shared key, the VPN connection, and a route to the radio:
CGW=$(aws ec2 create-customer-gateway --type ipsec.1 --ip-address 203.0.113.10 --bgp-asn 65000 --query CustomerGateway.CustomerGatewayId --output text)
head -c 32 /dev/urandom | base64 | tr -d '/+=' | head -c 31 | sed 's/^/k/' > radio-vpn.psk
T='{"IKEVersions":[{"Value":"ikev2"}],"PreSharedKey":"'$(cat radio-vpn.psk)'","Phase1EncryptionAlgorithms":[{"Value":"AES256"}],"Phase2EncryptionAlgorithms":[{"Value":"AES256"}],"Phase1IntegrityAlgorithms":[{"Value":"SHA2-256"}],"Phase2IntegrityAlgorithms":[{"Value":"SHA2-256"}],"Phase1DHGroupNumbers":[{"Value":14}],"Phase2DHGroupNumbers":[{"Value":14}]}'
VPN=$(aws ec2 create-vpn-connection --type ipsec.1 --customer-gateway-id $CGW --vpn-gateway-id $VGW \
--options '{"StaticRoutesOnly":true,"LocalIpv4NetworkCidr":"192.168.1.50/32","RemoteIpv4NetworkCidr":"172.31.0.0/16","TunnelOptions":['"$T,$T"']}' \
--query VpnConnection.VpnConnectionId --output text)
aws ec2 create-vpn-connection-route --vpn-connection-id $VPN --destination-cidr-block 192.168.1.50/32
GW=$(aws ec2 describe-vpn-connections --vpn-connection-ids $VPN --query 'VpnConnections[0].VgwTelemetry[0].OutsideIpAddress' --output text)
echo $GW
3. Create the instances¶
Create one instance per zone, at 10.128.0.10, 10.128.0.11 and 10.128.0.12:
i=10
for z in a b c; do
gcloud compute instances create ella-core-$z --zone us-central1-$z \
--machine-type e2-standard-2 --subnet default --private-network-ip 10.128.0.$i \
--no-address --tags ella-core \
--image-family ubuntu-2404-lts-amd64 --image-project ubuntu-os-cloud \
--boot-disk-size 20GB
i=$((i+1))
done
Create one instance per availability zone, at the tenth address of the zone's default subnet:
AMI=$(aws ssm get-parameter --name /aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id --query Parameter.Value --output text)
for z in a b c; do
read SUBNET CIDR <<< "$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$VPC Name=availability-zone,Values=us-east-1$z Name=default-for-az,Values=true --query 'Subnets[0].[SubnetId,CidrBlock]' --output text)"
ip=${CIDR%.*}.10
ID=$(aws ec2 run-instances --image-id $AMI --instance-type t3.medium --subnet-id $SUBNET --private-ip-address $ip \
--security-group-ids $SG --iam-instance-profile Name=ella-core-ssm \
--block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=20,VolumeType=gp3}' \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=ella-core-$z}]" \
--query 'Instances[0].InstanceId' --output text)
echo "ella-core-$z $ID $ip ${CIDR%.*}.1"
done
The output lists each instance's name, ID, address and gateway, e.g.:
ella-core-a i-0a1b2c3d4e5f60001 172.31.0.10 172.31.0.1
ella-core-b i-0a1b2c3d4e5f60002 172.31.80.10 172.31.80.1
ella-core-c i-0a1b2c3d4e5f60003 172.31.16.10 172.31.16.1
The following steps use these example values.
4. Install Ella Core¶
On each instance:
gcloud compute ssh ella-core-<z> --zone us-central1-<z> --tunnel-through-iap
Limit the size of packets sent to the radio, so they fit in the VPN tunnel:
sudo tee /etc/netplan/60-ella.yaml <<'EOF'
network:
version: 2
ethernets:
ens4:
routes:
- to: 192.168.1.50/32
via: 10.128.0.1
mtu: 1300
EOF
sudo chmod 600 /etc/netplan/60-ella.yaml
sudo netplan apply
Install Ella Core:
sudo snap install ella-core
Replace /var/snap/ella-core/common/core.yaml with the following, setting bind-address to the instance's address:
logging:
system:
level: "info"
output: "stdout"
audit:
output: "stdout"
db:
path: "/var/snap/ella-core/common/data/ella.db"
interfaces:
n2:
name: "ens4"
n3:
name: "ens4"
n6:
name: "ens4"
api:
address: "0.0.0.0"
port: 5002
tls:
cert: "/var/snap/ella-core/common/cert.pem"
key: "/var/snap/ella-core/common/key.pem"
datapath:
attach-mode: "tcx"
cluster:
bind-address: "10.128.0.10:7000"
Start Ella Core:
sudo snap start --enable ella-core.cored
On each instance:
aws ssm start-session --target <instance-id>
Limit the size of packets sent to the radio, so they fit in the VPN tunnel. Set via to the instance's gateway from step 3, e.g. 172.31.0.1:
sudo tee /etc/netplan/60-ella.yaml <<'EOF'
network:
version: 2
ethernets:
ens5:
routes:
- to: 192.168.1.50/32
via: 172.31.0.1
mtu: 1300
EOF
sudo chmod 600 /etc/netplan/60-ella.yaml
sudo netplan apply
Install Ella Core:
sudo snap install ella-core
Replace /var/snap/ella-core/common/core.yaml with the following, setting bind-address to the instance's address from step 3:
logging:
system:
level: "info"
output: "stdout"
audit:
output: "stdout"
db:
path: "/var/snap/ella-core/common/data/ella.db"
interfaces:
n2:
name: "ens5"
n3:
name: "ens5"
n6:
name: "ens5"
api:
address: "0.0.0.0"
port: 5002
tls:
cert: "/var/snap/ella-core/common/cert.pem"
key: "/var/snap/ella-core/common/key.pem"
datapath:
attach-mode: "tcx"
cluster:
bind-address: "172.31.0.10:7000"
Start Ella Core:
sudo snap start --enable ella-core.cored
5. Form the cluster¶
Open each node's UI through IAP, each in its own terminal:
gcloud compute start-iap-tunnel ella-core-a 5002 --local-host-port=localhost:5002 --zone us-central1-a
gcloud compute start-iap-tunnel ella-core-b 5002 --local-host-port=localhost:5003 --zone us-central1-b
gcloud compute start-iap-tunnel ella-core-c 5002 --local-host-port=localhost:5004 --zone us-central1-c
- Open
https://localhost:5002and initialize node a. - Open Cluster, click Add Node, click Mint Token, and copy the token.
- Open
https://localhost:5003, click Join an existing cluster instead, paste the token, enter10.128.0.10:7000, and click Join. - Repeat steps 2 and 3 for node c, at
https://localhost:5004.
Open each node's UI through Session Manager, each in its own terminal:
aws ssm start-session --target <instance-id-a> --document-name AWS-StartPortForwardingSession --parameters portNumber=5002,localPortNumber=5002
aws ssm start-session --target <instance-id-b> --document-name AWS-StartPortForwardingSession --parameters portNumber=5002,localPortNumber=5003
aws ssm start-session --target <instance-id-c> --document-name AWS-StartPortForwardingSession --parameters portNumber=5002,localPortNumber=5004
- Open
https://localhost:5002and initialize node a. - Open Cluster, click Add Node, click Mint Token, and copy the token.
- Open
https://localhost:5003, click Join an existing cluster instead, paste the token, enter node a's address from step 3 with port7000, e.g.172.31.0.10:7000, and click Join. - Repeat steps 2 and 3 for node c, at
https://localhost:5004.
On the Cluster page, the three nodes appear as Healthy.
6. Configure the network¶
In the UI:
- In Operator, set the MCC, MNC and TAC to match the radio and SIM cards.
- In Data Networks, open
internetand set its MTU to1300. - In Subscribers, add each SIM card.
7. Connect the radio¶
Give the radio a static address on the site network, e.g. 192.168.1.50/24 with gateway 192.168.1.1.
Configure an IPsec tunnel on the radio:
| Setting | Value |
|---|---|
| Peer | The VPN gateway address ($GW) |
| IKE version | IKEv2 |
| Authentication | The pre-shared key in radio-vpn.psk |
| Local ID | The site's public address, e.g. 203.0.113.10 |
| Remote ID | The VPN gateway address |
| Local subnet | 192.168.1.50/32 |
| Remote subnet | 10.128.0.0/20 |
| IKE and ESP | AES-256, SHA-256, DH group 14 (MODP 2048) |
| IKE lifetime | 10 h or less |
| IPsec SA lifetime | 3 h or less |
Set the radio's MME (4G) or AMF (5G) addresses to 10.128.0.10, 10.128.0.11 and 10.128.0.12, then restart the radio.
| Setting | Value |
|---|---|
| Peer | The tunnel's outside address ($GW) |
| IKE version | IKEv2 |
| Authentication | The pre-shared key in radio-vpn.psk |
| Local ID | The site's public address, e.g. 203.0.113.10 |
| Remote ID | The tunnel's outside address |
| Local subnet | 192.168.1.50/32 |
| Remote subnet | 172.31.0.0/16 |
| IKE and ESP | AES-256, SHA-256, DH group 14 (MODP 2048) |
| IKE lifetime | 8 h or less |
| IPsec SA lifetime | 1 h or less |
Set the radio's MME (4G) or AMF (5G) addresses to the three instance addresses from step 3, e.g. 172.31.0.10, 172.31.80.10 and 172.31.16.10.
8. Verify¶
Check that the tunnel is up:
gcloud compute vpn-tunnels describe radio-tunnel $R --format='value(status)'
The output is ESTABLISHED.
aws ec2 describe-vpn-connections --vpn-connection-ids $VPN --query 'VpnConnections[0].VgwTelemetry[0].Status' --output text
The output is UP.
The radio appears on the Radios page, and attached phones appear as Registered on the Subscribers page.

