@starship-ci/generator
Version:
Kubernetes manifest generator for Starship deployments
374 lines (369 loc) • 13.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EthereumStatefulSetGenerator = void 0;
const helpers = __importStar(require("../../../helpers"));
/**
* Generates the StatefulSet for Ethereum chain
* Based on the Helm template: chains/eth/statefulsets.yaml
*/
class EthereumStatefulSetGenerator {
config;
chain;
constructor(chain, config) {
this.config = config;
this.chain = chain;
}
generate() {
const name = `${this.chain.name}-${this.chain.id}`;
return [
{
apiVersion: 'apps/v1',
kind: 'StatefulSet',
metadata: {
name: name,
labels: {
...helpers.getCommonLabels(this.config),
app: name,
'app.kubernetes.io/component': 'chain',
'app.kubernetes.io/name': name,
'app.kubernetes.io/part-of': helpers.getChainId(this.chain),
'app.kubernetes.io/role': 'ethereum',
'starship.io/chain-name': this.chain.name,
'starship.io/chain-id': helpers.getChainId(this.chain)
}
},
spec: {
serviceName: name,
replicas: 1,
selector: {
matchLabels: {
'app.kubernetes.io/instance': name,
'app.kubernetes.io/name': name
}
},
template: {
metadata: {
annotations: {
quality: 'release',
role: 'api-gateway',
sla: 'high',
tier: 'gateway'
},
labels: {
'app.kubernetes.io/instance': name,
'app.kubernetes.io/type': name,
'app.kubernetes.io/name': name,
'app.kubernetes.io/rawname': String(this.chain.id)
}
},
spec: {
initContainers: this.createInitContainers(this.chain),
containers: this.createMainContainers(this.chain),
volumes: this.createVolumes()
}
}
}
}
];
}
createInitContainers(chain) {
const initContainers = [];
// Init Genesis Beacon container
initContainers.push(this.createInitGenesisBeaconContainer(chain));
// Init Genesis Execution container
initContainers.push(this.createInitGenesisExecutionContainer(chain));
return initContainers;
}
createInitGenesisBeaconContainer(chain) {
const prysmctlImage = chain.config?.prysmctl?.image ||
'ghcr.io/hyperweb-io/starship/prysm/cmd/prysmctl:v5.2.0';
const numValidators = chain.config?.validator?.numValidator || 1;
return {
name: 'init-genesis-beacon',
image: prysmctlImage,
imagePullPolicy: 'IfNotPresent',
command: ['bash', '-c'],
args: [
`
mkdir -p /ethereum/consensus /ethereum/execution
cp /config/genesis.json /ethereum/execution/genesis.json
cp /config/config.yaml /ethereum/consensus/config.yaml
echo "Initializing genesis"
prysmctl testnet generate-genesis \\
--fork=capella \\
--num-validators=${numValidators} \\
--genesis-time-delay=15 \\
--output-ssz=/ethereum/consensus/genesis.ssz \\
--chain-config-file=/ethereum/consensus/config.yaml \\
--geth-genesis-json-in=/ethereum/execution/genesis.json \\
--geth-genesis-json-out=/ethereum/execution/genesis.json
echo "Copy secrets over"
cp /config/jwt.hex /etc/secrets/jwt.hex
`.trim()
],
resources: this.getNodeResources(chain),
volumeMounts: [
{ name: 'secrets', mountPath: '/etc/secrets' },
{ name: 'config', mountPath: '/config' },
{ name: 'ethereum', mountPath: '/ethereum' }
]
};
}
createInitGenesisExecutionContainer(chain) {
return {
name: 'init-genesis-execution',
image: chain.image,
imagePullPolicy: 'IfNotPresent',
command: ['bash', '-c'],
args: [
`
echo "Initializing genesis geth"
geth --datadir /ethereum/execution init /ethereum/execution/genesis.json
`.trim()
],
resources: this.getNodeResources(chain),
volumeMounts: [
{ name: 'secrets', mountPath: '/etc/secrets' },
{ name: 'config', mountPath: '/config' },
{ name: 'ethereum', mountPath: '/ethereum' }
]
};
}
createMainContainers(chain) {
const containers = [];
// Geth container
containers.push(this.createGethContainer(chain));
// Beacon chain container
containers.push(this.createBeaconChainContainer(chain));
// Validator container
containers.push(this.createValidatorContainer(chain));
return containers;
}
createGethContainer(chain) {
return {
name: 'geth',
image: chain.image,
imagePullPolicy: 'IfNotPresent',
env: [
{ name: 'HTTP_PORT', value: '8545' },
{ name: 'WS_PORT', value: '8546' },
{ name: 'RPC_PORT', value: '8551' }
],
command: ['bash', '-c'],
args: [
`
echo "Setting UDP buffer size"
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
echo "Starting execution chain"
geth --datadir /ethereum/execution --http \\
--http.addr=0.0.0.0 \\
--http.port=$HTTP_PORT \\
--http.api=eth,net,web3,debug \\
--ws --ws.addr=0.0.0.0 \\
--ws.port=$WS_PORT \\
--authrpc.addr=0.0.0.0 \\
--authrpc.port=$RPC_PORT \\
--nodiscover \\
--http.corsdomain=* \\
--ws.api=eth,net,web3 \\
--ws.origins=* \\
--http.vhosts=* \\
--authrpc.vhosts=* \\
--authrpc.jwtsecret=/etc/secrets/jwt.hex \\
--unlock=0x123463a4B065722E99115D6c222f267d9cABb524 \\
--password=/dev/null \\
--syncmode=snap \\
--snapshot=false \\
--networkid=${chain.id} \\
--verbosity=4 \\
--maxpeers=50 \\
--nat=none \\
--log.vmodule=engine=6
`.trim()
],
resources: this.getNodeResources(chain),
volumeMounts: [
{ name: 'ethereum', mountPath: '/ethereum' },
{ name: 'secrets', mountPath: '/etc/secrets' }
],
readinessProbe: {
exec: {
command: [
'/bin/bash',
'-c',
`curl -s --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' -H "Content-Type: application/json" -X POST http://localhost:8545 | grep -q '"result":false'`
]
},
initialDelaySeconds: 15,
periodSeconds: 10
}
};
}
createBeaconChainContainer(chain) {
const beaconImage = chain.config?.beacon?.image ||
'ghcr.io/hyperweb-io/starship/prysm/beacon-chain:v5.2.0';
return {
name: 'beacon-chain',
image: beaconImage,
imagePullPolicy: 'Always',
env: [
{
name: 'NAMESPACE',
valueFrom: {
fieldRef: {
fieldPath: 'metadata.namespace'
}
}
}
],
command: ['bash', '-c'],
args: [
`
echo "Waiting 30 seconds for execution client to be ready..."
sleep 30
echo "Starting consensus chain"
beacon-chain \\
--execution-endpoint=http://0.0.0.0:8551 \\
--jwt-secret=/etc/secrets/jwt.hex \\
--accept-terms-of-use \\
--http-host 0.0.0.0 \\
--rpc-host 0.0.0.0 \\
--chain-id ${chain.id} \\
--contract-deployment-block=0 \\
--datadir /ethereum/consensus \\
--genesis-state /ethereum/consensus/genesis.ssz \\
--min-sync-peers=0 \\
--chain-config-file=/ethereum/consensus/config.yaml \\
--network-id ${chain.id} \\
--suggested-fee-recipient=0x123463a4B065722E99115D6c222f267d9cABb524 \\
--minimum-peers-per-subnet=0 \\
--force-clear-db
`.trim()
],
resources: this.getNodeResources(chain),
volumeMounts: [
{ name: 'ethereum', mountPath: '/ethereum' },
{ name: 'secrets', mountPath: '/etc/secrets' }
],
readinessProbe: {
httpGet: {
path: '/eth/v1/node/health',
port: '3500'
},
initialDelaySeconds: 15,
periodSeconds: 20
}
};
}
createValidatorContainer(chain) {
const validatorImage = chain.config?.validator?.image ||
'ghcr.io/hyperweb-io/starship/prysm/validator:v5.2.0';
const numValidators = chain.config?.validator?.numValidator || 1;
return {
name: 'validator',
image: validatorImage,
imagePullPolicy: 'Always',
env: [
{
name: 'NAMESPACE',
valueFrom: {
fieldRef: {
fieldPath: 'metadata.namespace'
}
}
}
],
command: ['bash', '-c'],
args: [
`
echo "Waiting 15 seconds for execution client to be ready..."
sleep 20
mkdir -p /ethereum/consensus/validator
echo "Starting validator node"
validator \\
--accept-terms-of-use \\
--beacon-rpc-provider=0.0.0.0:4000 \\
--datadir=/ethereum/consensus/validator \\
--interop-num-validators=${numValidators} \\
--interop-start-index=0 \\
--force-clear-db \\
--grpc-gateway-host=0.0.0.0 \\
--chain-config-file=/ethereum/consensus/config.yaml \\
--monitoring-host=0.0.0.0 \\
--monitoring-port=8081 \\
--suggested-fee-recipient=0x0C46c2cAFE097b4f7e1BB868B89e5697eE65f934
`.trim()
],
resources: this.getNodeResources(chain),
volumeMounts: [
{ name: 'ethereum', mountPath: '/ethereum' },
{ name: 'secrets', mountPath: '/etc/secrets' }
],
readinessProbe: {
httpGet: {
path: '/metrics',
port: '8081'
},
initialDelaySeconds: 20,
periodSeconds: 30
}
};
}
createVolumes() {
return [
{
name: 'config',
configMap: {
name: 'config-ethereum'
}
},
{
name: 'ethereum',
emptyDir: {}
},
{
name: 'secrets',
emptyDir: {}
}
];
}
getNodeResources(chain) {
// Use default resources or chain-specific resources
const defaultResources = this.config.resources?.node;
return {
requests: {
cpu: chain.resources?.cpu || defaultResources?.cpu,
memory: chain.resources?.memory || defaultResources?.memory
},
limits: {
cpu: chain.resources?.cpu || defaultResources?.cpu,
memory: chain.resources?.memory || defaultResources?.memory
}
};
}
}
exports.EthereumStatefulSetGenerator = EthereumStatefulSetGenerator;