--- # tasks file for monero - name: Check required variables ansible.builtin.assert: that: - monerod_user | length > 0 - monerod_password | length > 0 - monero_p2pool_wallet_address | length > 0 - monero_p2pool_chain in ['main', 'mini', 'nano'] quiet: true no_log: true - name: Create Monero namespace k8s: state: present definition: apiVersion: v1 kind: Namespace metadata: name: monero - name: Create PVC for Monero k8s: state: present definition: apiVersion: v1 kind: PersistentVolumeClaim metadata: name: data namespace: monero spec: storageClassName: r720-nfs-client accessModes: - ReadWriteOnce resources: requests: storage: 512Gi - name: Create PVC for P2Pool k8s: state: present definition: apiVersion: v1 kind: PersistentVolumeClaim metadata: name: p2pool namespace: monero spec: storageClassName: r720-nfs-client accessModes: - ReadWriteOnce resources: requests: storage: 64Gi - name: Request a certificate for Monero k8s: state: present definition: apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: monerotls namespace: monero spec: secretName: monerotls privateKey: algorithm: RSA encoding: PKCS1 size: 2048 duration: 2160h # 90d renewBefore: 360h # 15d isCA: false usages: - server auth - client auth subject: organizations: - EOM commonName: monerod.eom.dev dnsNames: - monerod.eom.dev issuerRef: name: ca-issuer kind: ClusterIssuer - name: Request a certificate for p2pool k8s: state: present definition: apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: tls namespace: monero spec: secretName: tls privateKey: algorithm: RSA encoding: PKCS1 size: 2048 duration: 2160h # 90d renewBefore: 360h # 15d isCA: false usages: - server auth - client auth subject: organizations: - EOM commonName: p2pool.eom.dev dnsNames: - p2pool.eom.dev issuerRef: name: ca-issuer kind: ClusterIssuer # Kept out of the Deployments so the login doesn't show in their specs. - name: Store the monerod RPC login k8s: state: present definition: apiVersion: v1 kind: Secret metadata: name: "{{ monerod_rpc_secret_name }}" namespace: monero type: Opaque stringData: login: "{{ monerod_user }}:{{ monerod_password }}" no_log: true - name: Edit Deployment for Monerod k8s: definition: apiVersion: apps/v1 kind: Deployment metadata: name: monerod namespace: monero spec: replicas: 1 selector: matchLabels: app: monerod template: metadata: labels: app: monerod spec: containers: - name: monerod image: ericomeehan/monerod:latest imagePullPolicy: Always command: - monerod args: - "--p2p-bind-ip=0.0.0.0" - "--p2p-bind-port=18080" - "--rpc-bind-ip=0.0.0.0" - "--rpc-bind-port=18081" - "--restricted-rpc" - "--rpc-login=$(MONEROD_RPC_LOGIN)" - "--data-dir=/data" - "--zmq-pub=tcp://0.0.0.0:18083" # P2Pool connects from its own pod # autodetect: Shkeeper only speaks plain HTTP to monerod (it never sets # protocol=https), so the port must accept both. - "--rpc-ssl=autodetect" - "--rpc-ssl-private-key=/etc/letsencrypt/tls.key" - "--rpc-ssl-certificate=/etc/letsencrypt/tls.crt" - "--non-interactive" - "--no-igd" - "--confirm-external-bind" - "--bootstrap-daemon-address=auto" env: - name: MONEROD_RPC_LOGIN valueFrom: secretKeyRef: name: "{{ monerod_rpc_secret_name }}" key: login resources: requests: memory: 2Gi ports: - containerPort: 18080 - containerPort: 18081 - containerPort: 18083 volumeMounts: - name: monerod mountPath: /data - name: monerotls mountPath: /etc/letsencrypt volumes: - name: monerod persistentVolumeClaim: claimName: data - name: monerotls secret: secretName: monerotls # Mounted into the stats sidecar; the pod annotation below rolls it on change. - name: Store the P2Pool stats sidecar script k8s: state: present definition: apiVersion: v1 kind: ConfigMap metadata: name: p2pool-stats namespace: monero data: p2pool_stats.py: "{{ lookup('ansible.builtin.file', 'p2pool_stats.py') }}" - name: deploy p2pool k8s: definition: apiVersion: apps/v1 kind: Deployment metadata: name: p2pool namespace: monero spec: replicas: 1 selector: matchLabels: app: p2pool template: metadata: labels: app: p2pool annotations: monero/stats-script: "{{ lookup('ansible.builtin.file', 'p2pool_stats.py') | hash('sha256') }}" spec: containers: - name: p2pool image: ericomeehan/p2pool:latest imagePullPolicy: IfNotPresent command: - p2pool # P2Pool takes "--opt value" only; "--opt=value" makes it exit. args: >- {{ ['--host', 'monerod', '--rpc-port', '18081', '--wallet', monero_p2pool_wallet_address, '--stratum', '0.0.0.0:3333', '--p2p', '0.0.0.0:37889', # JSON stats for the sidecar below. local/stratum lists # worker IPs, so /api is never served directly. '--data-api', '/api', '--local-api'] + ([] if monero_p2pool_chain == 'main' else ['--' ~ monero_p2pool_chain]) + (['--addpeers', monero_p2pool_addpeers | join(',')] if monero_p2pool_addpeers else []) + ['--tls-cert', '/etc/letsencrypt/tls.crt', '--tls-cert-key', '/etc/letsencrypt/tls.key', '--rpc-ssl', '--rpc-login', '$(MONEROD_RPC_LOGIN)'] }} env: - name: MONEROD_RPC_LOGIN valueFrom: secretKeyRef: name: "{{ monerod_rpc_secret_name }}" key: login ports: - containerPort: 3333 - containerPort: 37889 volumeMounts: - name: p2pool mountPath: /home/p2pool - name: tls mountPath: /etc/letsencrypt - name: api mountPath: /api # Serves an allowlisted summary of /api at /p2pool/stats.json. - name: stats image: "{{ monero_p2pool_stats_image }}" command: ["python3", "-I", "/app/p2pool_stats.py"] env: - name: P2POOL_API_DIR value: /api - name: P2POOL_CHAIN value: "{{ monero_p2pool_chain }}" - name: PYTHONDONTWRITEBYTECODE value: "1" ports: - containerPort: 8080 name: stats readinessProbe: httpGet: path: /healthz port: stats resources: requests: cpu: 10m memory: 32Mi limits: memory: 64Mi securityContext: runAsNonRoot: true runAsUser: 65534 runAsGroup: 65534 readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: [ALL] volumeMounts: - name: api mountPath: /api readOnly: true - name: stats-app mountPath: /app readOnly: true volumes: - name: p2pool persistentVolumeClaim: claimName: p2pool - name: tls secret: secretName: tls - name: api emptyDir: sizeLimit: 64Mi - name: stats-app configMap: name: p2pool-stats - name: monero service k8s: definition: apiVersion: v1 kind: Service metadata: name: monerod namespace: monero spec: selector: app: monerod ports: - port: 18080 name: p2p - port: 18081 name: rpc - port: 18083 name: zmq type: LoadBalancer - name: p2pool service k8s: definition: apiVersion: v1 kind: Service metadata: name: p2pool namespace: monero spec: selector: app: p2pool ports: - port: 3333 name: mine - port: 37889 name: p2poolp2p type: LoadBalancer - name: p2pool stats service k8s: definition: apiVersion: v1 kind: Service metadata: name: p2pool-stats namespace: monero spec: selector: app: p2pool ports: - port: 8080 targetPort: stats name: http type: ClusterIP # Adds one exact path to the website's host. ingress-nginx merges it into that # host's server block, so it reuses the website's TLS certificate and the # homepage can fetch it same-origin. Nothing else on the host is affected. - name: Publish P2Pool stats on the website host k8s: state: "{{ 'present' if monero_p2pool_stats_host | length > 0 else 'absent' }}" definition: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: p2pool-stats namespace: monero annotations: nginx.ingress.kubernetes.io/enable-access-log: "false" spec: ingressClassName: "{{ monero_p2pool_stats_ingress_class }}" rules: - host: "{{ monero_p2pool_stats_host | default('unset', true) }}" http: paths: - path: /p2pool/stats.json pathType: Exact backend: service: name: p2pool-stats port: number: 8080