Production
This commit is contained in:
+258
-35
@@ -1,45 +1,268 @@
|
||||
- name: Deploy Wikideck
|
||||
- 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: Require an explicitly approved, immutable mainnet release
|
||||
- name: Validate candidate cluster inputs
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- wikideck_mainnet_deployment_approved | default(false) | bool
|
||||
- wikideck_release_image is defined and wikideck_release_image is string and wikideck_release_image is match('^.+@sha256:[a-f0-9]{64}$')
|
||||
- wikideck_mainnet_peers is defined and wikideck_mainnet_peers is sequence and wikideck_mainnet_peers is not string and wikideck_mainnet_peers | length >= 2
|
||||
- wikideck_mainnet_peers | unique | list | length == wikideck_mainnet_peers | length
|
||||
- wikideck_candidate_chain_id is defined and wikideck_candidate_chain_id is match('^[a-f0-9]{64}$')
|
||||
- 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'
|
||||
- wikideck_release_genesis_time is defined and (wikideck_release_genesis_time | string) is match('^[1-9][0-9]{8,10}$')
|
||||
- wikideck_release_genesis_time | int >= 1700000000 and wikideck_release_genesis_time | int <= 4102444800
|
||||
- wikideck_release_genesis_time | int <= ansible_date_time.epoch | int
|
||||
fail_msg: >-
|
||||
Mainnet is currently NO-GO. Do not deploy until the operator has approved
|
||||
a digest-pinned image with a verified frozen manifest, arrived genesis,
|
||||
at least two independent public HTTPS peers, and explicit risk acceptance.
|
||||
roles:
|
||||
- role: ericomeehan.wikideck
|
||||
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_image: "{{ wikideck_release_image }}"
|
||||
wikideck_image_pull_policy: IfNotPresent
|
||||
wikideck_peers: "{{ wikideck_mainnet_peers }}"
|
||||
wikideck_mainnet_launch: "{{ wikideck_candidate_chain_id }}"
|
||||
wikideck_mainnet_risk_ack: "{{ wikideck_candidate_risk_ack }}"
|
||||
wikideck_public_origin: https://wikideck.eom.dev
|
||||
wikideck_ingress_enabled: true
|
||||
wikideck_ingress_host: wikideck.eom.dev
|
||||
wikideck_ingress_class_name: nginx
|
||||
wikideck_ingress_annotations:
|
||||
cert-manager.io/cluster-issuer: ca-issuer
|
||||
# Match the actual ingress controller namespace; add its podSelector if known.
|
||||
wikideck_network_policy_ingress_from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: ingress-nginx
|
||||
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([]) }}"
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user