269 lines
12 KiB
YAML
269 lines
12 KiB
YAML
- name: Deploy a three-node private Wikideck mainnet candidate
|
|
hosts: alpha-control-plane
|
|
become: true
|
|
vars_files:
|
|
- ~/Desktop/secrets.yaml
|
|
vars:
|
|
# This is an operator-controlled candidate, not an independent public network.
|
|
wikideck_release_image: ericomeehan/wikideck:latest
|
|
wikideck_candidate_risk_ack: operator-accepts-unaudited-risk
|
|
wikideck_cluster_nodes:
|
|
- wikideck
|
|
- wikideck-peer-a
|
|
- wikideck-peer-b
|
|
wikideck_namespace: wikideck
|
|
wikideck_image: "{{ wikideck_release_image }}"
|
|
wikideck_image_pull_policy: Always
|
|
# Roll out latest only when the inspected image changes, not on every run.
|
|
wikideck_pod_annotations:
|
|
wikideck.eom.dev/image-id: "{{ wikideck_manifest_image_id }}"
|
|
wikideck_allow_private_peers: true
|
|
wikideck_mainnet_launch: "{{ wikideck_candidate_chain_id }}"
|
|
wikideck_mainnet_risk_ack: "{{ wikideck_candidate_risk_ack }}"
|
|
# Internal validators share the browser origin, but only the first has ingress.
|
|
wikideck_public_origin: https://wikideck.eom.dev
|
|
wikideck_ingress_host: wikideck.eom.dev
|
|
wikideck_ingress_class_name: nginx
|
|
wikideck_ingress_annotations:
|
|
cert-manager.io/cluster-issuer: ca-issuer
|
|
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
|
wikideck_storage_size: 2Ti
|
|
wikideck_storage_class: nfs-client
|
|
# Populate only after confirming the real proxy source IPs/CIDRs.
|
|
wikideck_trusted_proxies: "{{ wikideck_ingress_proxy_sources | default([]) }}"
|
|
# Start every node before waiting; the first must not wait for absent peers.
|
|
wikideck_wait: false
|
|
wikideck_wait_timeout: 900
|
|
wikideck_network_policy_ingress_from:
|
|
- podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: wikideck
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: ingress-nginx
|
|
# Mining templates fetch Wikipedia articles and Wikimedia statistics over HTTPS.
|
|
# Standard NetworkPolicy cannot select FQDNs; permit public TCP 443, not private ranges.
|
|
wikideck_network_policy_https_to:
|
|
- ipBlock:
|
|
cidr: 0.0.0.0/0
|
|
except:
|
|
- 10.0.0.0/8
|
|
- 100.64.0.0/10
|
|
- 127.0.0.0/8
|
|
- 169.254.0.0/16
|
|
- 172.16.0.0/12
|
|
- 192.168.0.0/16
|
|
- ipBlock:
|
|
cidr: "::/0"
|
|
except:
|
|
- "::1/128"
|
|
- "fc00::/7"
|
|
- "fe80::/10"
|
|
wikideck_network_policy_peer_egress:
|
|
- to:
|
|
- podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: wikideck
|
|
ports:
|
|
- protocol: TCP
|
|
port: 3001
|
|
pre_tasks:
|
|
- name: Validate candidate cluster inputs
|
|
ansible.builtin.assert:
|
|
that:
|
|
- not ansible_check_mode
|
|
- wikideck_release_image is string and wikideck_release_image is match('^[^\s@]+:latest$')
|
|
- wikideck_cluster_nodes is sequence and wikideck_cluster_nodes is not string and wikideck_cluster_nodes is not mapping
|
|
- wikideck_cluster_nodes | length == 3
|
|
- wikideck_cluster_nodes | unique | list | length == 3
|
|
- wikideck_cluster_nodes | select('match', '^[a-z]([a-z0-9-]*[a-z0-9])?$') | list | length == 3
|
|
- wikideck_cluster_nodes | map('length') | max <= 50
|
|
- wikideck_candidate_risk_ack | default('') == 'operator-accepts-unaudited-risk'
|
|
fail_msg: >-
|
|
This candidate playbook requires a latest-tag image, three unique node
|
|
names, and operator-accepts-unaudited-risk. Check mode cannot inspect the
|
|
image manifest; use the offline render tests instead.
|
|
run_once: true
|
|
|
|
- name: Initialize manifest registry Secret references
|
|
ansible.builtin.set_fact:
|
|
wikideck_manifest_pull_secrets: []
|
|
run_once: true
|
|
|
|
- name: Convert registry Secret names to Kubernetes references
|
|
ansible.builtin.set_fact:
|
|
wikideck_manifest_pull_secrets: "{{ wikideck_manifest_pull_secrets + [{'name': item}] }}"
|
|
loop: "{{ wikideck_image_pull_secrets | default([]) }}"
|
|
run_once: true
|
|
|
|
- name: Create namespace for read-only image inspection
|
|
kubernetes.core.k8s:
|
|
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
|
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
|
state: present
|
|
apply: true
|
|
definition:
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: "{{ wikideck_namespace }}"
|
|
run_once: true
|
|
|
|
- name: Inspect latest without opening a ledger or starting a validator
|
|
run_once: true
|
|
block:
|
|
- name: Remove a stale manifest inspection Job
|
|
kubernetes.core.k8s:
|
|
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
|
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
|
state: absent
|
|
api_version: batch/v1
|
|
kind: Job
|
|
namespace: "{{ wikideck_namespace }}"
|
|
name: wikideck-candidate-manifest
|
|
wait: true
|
|
|
|
- name: Run the image manifest command
|
|
kubernetes.core.k8s:
|
|
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
|
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
|
state: present
|
|
wait: true
|
|
wait_timeout: 300
|
|
wait_condition:
|
|
type: Complete
|
|
status: "True"
|
|
definition:
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: wikideck-candidate-manifest
|
|
namespace: "{{ wikideck_namespace }}"
|
|
spec:
|
|
backoffLimit: 0
|
|
activeDeadlineSeconds: 240
|
|
ttlSecondsAfterFinished: 600
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
enableServiceLinks: false
|
|
imagePullSecrets: "{{ wikideck_manifest_pull_secrets }}"
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 1000
|
|
runAsGroup: 1000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
containers:
|
|
- name: manifest
|
|
image: "{{ wikideck_release_image }}"
|
|
imagePullPolicy: Always
|
|
command:
|
|
- node
|
|
- --experimental-strip-types
|
|
- server/index.ts
|
|
- --network-manifest
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
capabilities:
|
|
drop: [ALL]
|
|
resources:
|
|
requests: {cpu: 100m, memory: 128Mi}
|
|
limits: {cpu: "1", memory: 512Mi}
|
|
|
|
- name: Locate the completed manifest pod
|
|
kubernetes.core.k8s_info:
|
|
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
|
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
|
kind: Pod
|
|
namespace: "{{ wikideck_namespace }}"
|
|
label_selectors:
|
|
- job-name=wikideck-candidate-manifest
|
|
register: wikideck_manifest_pods
|
|
|
|
- name: Require exactly one completed manifest pod
|
|
ansible.builtin.assert:
|
|
that:
|
|
- wikideck_manifest_pods.resources | length == 1
|
|
- wikideck_manifest_pods.resources[0].status.phase == 'Succeeded'
|
|
|
|
- name: Read the public image manifest
|
|
kubernetes.core.k8s_log:
|
|
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
|
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
|
namespace: "{{ wikideck_namespace }}"
|
|
name: "{{ wikideck_manifest_pods.resources[0].metadata.name }}"
|
|
container: manifest
|
|
register: wikideck_manifest_log
|
|
|
|
- name: Parse the image identity
|
|
ansible.builtin.set_fact:
|
|
wikideck_candidate_manifest: "{{ wikideck_manifest_log.log | from_json }}"
|
|
|
|
- name: Require a compatible image and an arrived image-baked genesis
|
|
ansible.builtin.assert:
|
|
that:
|
|
- wikideck_candidate_manifest.profile == 'mainnet'
|
|
- wikideck_candidate_manifest.network == 'wikideck-randomx-mainnet-1'
|
|
- wikideck_candidate_manifest.privatePeersSupported | default(false) == true
|
|
- wikideck_candidate_manifest.chainId is string and wikideck_candidate_manifest.chainId is match('^[a-f0-9]{64}$')
|
|
- wikideck_candidate_manifest.genesisTime is integer
|
|
- wikideck_candidate_manifest.genesisTime >= 1700000000 and wikideck_candidate_manifest.genesisTime <= 4102444800
|
|
- wikideck_candidate_manifest.genesisTime <= ansible_date_time.epoch | int
|
|
fail_msg: >-
|
|
Rebuild and publish latest from code supporting private candidate
|
|
peers, with a deliberately chosen, arrived mainnet genesis frozen
|
|
into the image. Runtime genesis overrides are not supported.
|
|
|
|
- name: Generate the candidate chain approval from this image
|
|
ansible.builtin.set_fact:
|
|
wikideck_candidate_chain_id: "{{ wikideck_candidate_manifest.chainId }}"
|
|
wikideck_release_genesis_time: "{{ wikideck_candidate_manifest.genesisTime }}"
|
|
wikideck_manifest_image_id: "{{ wikideck_manifest_pods.resources[0].status.containerStatuses[0].imageID }}"
|
|
always:
|
|
- name: Remove the manifest inspection Job and its pod
|
|
kubernetes.core.k8s:
|
|
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
|
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
|
state: absent
|
|
api_version: batch/v1
|
|
kind: Job
|
|
namespace: "{{ wikideck_namespace }}"
|
|
name: wikideck-candidate-manifest
|
|
wait: true
|
|
tasks:
|
|
- name: Deploy each validator with its own service and persistent ledger
|
|
ansible.builtin.include_role:
|
|
# Use this updated checkout rather than an older installed Galaxy role.
|
|
name: "{{ playbook_dir }}/.." # noqa: role-name[path]
|
|
vars:
|
|
wikideck_name: "{{ wikideck_cluster_node }}"
|
|
wikideck_existing_claim: ""
|
|
wikideck_ingress_enabled: "{{ wikideck_cluster_node == wikideck_cluster_nodes[0] }}"
|
|
wikideck_peers: >-
|
|
{{ wikideck_cluster_nodes | reject('equalto', wikideck_cluster_node)
|
|
| map('regex_replace', '^', 'http://')
|
|
| map('regex_replace', '$', '.' ~ wikideck_namespace ~ '.svc.cluster.local:80') | list }}
|
|
loop: "{{ wikideck_cluster_nodes }}"
|
|
loop_control:
|
|
loop_var: wikideck_cluster_node
|
|
run_once: true
|
|
|
|
- name: Wait for all three validators after creating the full peer topology
|
|
kubernetes.core.k8s_info:
|
|
kubeconfig: "{{ wikideck_kubeconfig | default(omit, true) }}"
|
|
context: "{{ wikideck_kube_context | default(omit, true) }}"
|
|
api_version: apps/v1
|
|
kind: Deployment
|
|
namespace: "{{ wikideck_namespace }}"
|
|
name: "{{ item }}"
|
|
wait: true
|
|
wait_timeout: "{{ wikideck_wait_timeout | int }}"
|
|
wait_condition:
|
|
type: Available
|
|
status: "True"
|
|
loop: "{{ wikideck_cluster_nodes }}"
|
|
run_once: true
|