Skip to content

Chainstack ansible

Based on the code snippets provided, here is the consolidated and structured Ansible Playbook (site.yml) and the necessary supporting files.

I have organized the fragments into a logical execution flow: 1. Setup: OS dependencies, Kernel tuning, Swap. 2. Kubernetes: Install K3s, Configure Traefik (TLS), Install Kyverno. 3. Application: Install Chainstack Control Panel via cpctl. 4. Networking: Configure Ingress and API routing. 5. Finalization: Save credentials.

1. Main Playbook: site.yml

- name: Deploy Chainstack Control Panel on Ubuntu/K3s
  hosts: all
  become: true
  vars:
    # --- Configuration Variables ---
    # Update these values or pass them via -e
    chainstack_version: "1.0.0"
    chainstack_namespace: "control-panel"
    chainstack_release: "cp"
    chainstack_storage_class: "local-path" # Default for K3s
    chainstack_install_dir: "/opt/chainstack"

    # Domain Configuration
    final_domain: "chainstack.example.com"
    temp_domain: "" # Optional temporary domain

    # TLS Configuration
    certbot_email: "[email protected]"

    # Kyverno Policy Path
    kyverno_policy_file: "files/chainstack-node-policy.yaml"

    # Derived Paths
    playbook_dir: "{{ playbook_dir }}"
    helm_install_script: "https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3"

  tasks:
    # 1. OS Preparation
    - name: Install base dependencies
      ansible.builtin.import_tasks: tasks/dependencies.yml

    - name: Configure Kernel Parameters (Sysctl)
      ansible.builtin.template:
        src: templates/99-chainstack.conf.j2
        dest: /etc/sysctl.d/99-chainstack.conf
        mode: "0644"
      notify: reload sysctl

    - name: Create Swapfile
      ansible.builtin.import_tasks: tasks/swapfile.yml

    # 2. Kubernetes Cluster Setup
    - name: Install K3s
      ansible.builtin.import_tasks: tasks/k3s.yml

    - name: Configure Traefik for Let's Encrypt
      ansible.builtin.import_tasks: tasks/traefik.yml

    # 3. Security & Policy (Kyverno)
    - name: Install Kyverno
      ansible.builtin.import_tasks: tasks/kyverno.yml

    # 4. Chainstack Control Panel Installation
    - name: Install Chainstack Control Panel
      ansible.builtin.import_tasks: tasks/install_cp.yml

    # 5. Networking & Ingress
    - name: Create Ingress for Chainstack
      ansible.builtin.import_tasks: tasks/ingress.yml

    # 6. Post-Installation
    - name: Save Chainstack credentials
      ansible.builtin.import_tasks: tasks/password.yml

  handlers:
    - name: reload sysctl
      ansible.builtin.command: sysctl --system
      changed_when: true

2. Task Files

Ensure the directory structure is tasks/ and templates/.

tasks/dependencies.yml

- name: Wait for apt/dpkg lock
  ansible.builtin.shell: |
    while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || \
          fuser /var/lib/apt/lists/lock >/dev/null 2>&1 || \
          fuser /var/cache/apt/archives/lock >/dev/null 2>&1; do
      sleep 5
    done
  changed_when: false

- name: Install base packages
  ansible.builtin.apt:
    name:
      - curl
      - wget
      - git
      - jq
      - ca-certificates
      - openssl
    state: present
    update_cache: true
  register: apt_result
  retries: 10
  delay: 15
  until: apt_result is succeeded

- name: Install kubectl
  ansible.builtin.shell: |
    curl -LO https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl
    install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
    rm kubectl
  args:
    creates: /usr/local/bin/kubectl

- name: Install Helm
  ansible.builtin.shell: |
    curl -fsSL {{ helm_install_script }} | bash
  args:
    creates: /usr/local/bin/helm

- name: Install yq
  ansible.builtin.get_url:
    url: https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
    dest: /usr/local/bin/yq
    mode: "0755"
    force: false

tasks/k3s.yml

- name: Install k3s
  shell: |
    curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik --flannel-backend=none" sh -
  args:
    creates: /usr/local/bin/k3s
  # Note: We disable default traefik to manage it manually via manifests for better control

- name: Wait for k3s service
  service_facts:

- name: Ensure k3s service started
  service:
    name: k3s
    state: started
    enabled: true

- name: Wait for Kubernetes API
  shell: |
    curl -k https://127.0.0.1:6443/healthz
  register: api_ready
  retries: 30
  delay: 5
  until: api_ready.rc == 0

- name: Configure kubeconfig for kubectl
  file:
    path: /root/.kube
    state: directory
    mode: "0700"

- name: Copy kubeconfig
  copy:
    src: /etc/rancher/k3s/k3s.yaml
    dest: /root/.kube/config
    remote_src: true
    mode: "0600"

- name: Wait for node Ready
  shell: |
    kubectl get nodes --no-headers | grep -q " Ready"
  environment:
    KUBECONFIG: /etc/rancher/k3s/k3s.yaml
  register: k3s_ready
  retries: 20
  delay: 10
  until: k3s_ready.rc == 0

tasks/traefik.yml

- name: Configure Traefik Let's Encrypt
  copy:
    dest: /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
    mode: "0644"
    content: |
      apiVersion: helm.cattle.io/v1
      kind: HelmChartConfig
      metadata:
        name: traefik
        namespace: kube-system
      spec:
        valuesContent: |-
          additionalArguments:
            - "--certificatesresolvers.le.acme.email={{ certbot_email }}"
            - "--certificatesresolvers.le.acme.storage=/data/acme.json"
            - "--certificatesresolvers.le.acme.tlschallenge=true"
            - "--entrypoints.web.address=:80"
            - "--entrypoints.websecure.address=:443"

tasks/kyverno.yml

- name: Add Kyverno Helm repo
  kubernetes.core.helm_repository:
    name: kyverno
    repo_url: https://kyverno.github.io/kyverno/

- name: Install Kyverno
  kubernetes.core.helm:
    name: kyverno
    chart_ref: kyverno/kyverno
    release_namespace: kyverno
    create_namespace: true
    wait: true
    wait_timeout: 600s

- name: Wait for Kyverno pods
  kubernetes.core.k8s_info:
    kind: Pod
    namespace: kyverno
  register: kyverno_pods
  until: >
    kyverno_pods.resources |
    selectattr('status.phase','equalto','Running') |
    list | length > 0
  retries: 20
  delay: 10

- name: Load policy
  set_fact:
    chainstack_policy: "{{ lookup('file', playbook_dir + '/' + kyverno_policy_file) | from_yaml }}"

- name: Apply Chainstack resource mutation policy
  kubernetes.core.k8s:
    state: present
    apply: true
    definition: "{{ chainstack_policy }}"

tasks/install_cp.yml

- name: Create install directory
  ansible.builtin.file:
    path: "{{ chainstack_install_dir }}"
    state: directory
    mode: "0755"

- name: Copy cpctl installer
  ansible.builtin.copy:
    src: cpctl.sh
    dest: "{{ chainstack_install_dir }}/cpctl"
    mode: "0755"

- name: Check if Chainstack Helm release exists
  ansible.builtin.shell: >
    helm status {{ chainstack_release }} -n {{ chainstack_namespace }}
  register: cp_release
  failed_when: false
  changed_when: false

- name: Install Chainstack Control Plane
  ansible.builtin.shell: |
    {{ chainstack_install_dir }}/cpctl install \
      -v {{ chainstack_version }} \
      -s {{ chainstack_storage_class }} \
      -y
  args:
    chdir: "{{ chainstack_install_dir }}"
  register: cp_install
  when: cp_release.rc != 0
  no_log: true

- name: Wait for Control Plane deployments
  ansible.builtin.shell: |
    kubectl wait --for=condition=available deployment --all \
      -n {{ chainstack_namespace }} \
      --timeout=600s
  register: cp_ready
  changed_when: false

tasks/ingress.yml

- name: Install python3-pip
  ansible.builtin.apt:
    name: python3-pip
    state: present
    update_cache: true
    lock_timeout: 600

- name: Install Kubernetes Python client
  ansible.builtin.pip:
    name:
      - kubernetes
      - openshift
      - PyYAML
    executable: pip3
    state: present

- name: Create Chainstack API strip middleware
  kubernetes.core.k8s:
    state: present
    apply: true
    kubeconfig: /etc/rancher/k3s/k3s.yaml
    definition:
      apiVersion: traefik.io/v1alpha1
      kind: Middleware
      metadata:
        name: chainstack-api-strip
        namespace: control-panel
      spec:
        stripPrefix:
          prefixes:
            - /api

- name: Build list of Chainstack ingress domains
  ansible.builtin.set_fact:
    chainstack_ingress_domains: >-
      {{
        [final_domain, temp_domain | default('')]
        | map('trim')
        | reject('equalto', '')
        | unique
        | list
      }}

- name: Create Chainstack HTTPS ingress
  kubernetes.core.k8s:
    state: present
    kubeconfig: /etc/rancher/k3s/k3s.yaml
    api_version: networking.k8s.io/v1
    kind: Ingress
    definition:
      metadata:
        name: "chainstack-ui-{{ item | regex_replace('[^a-zA-Z0-9-]', '-') }}"
        namespace: control-panel
        annotations:
          traefik.ingress.kubernetes.io/router.entrypoints: websecure
          traefik.ingress.kubernetes.io/router.tls: "true"
          traefik.ingress.kubernetes.io/router.tls.certresolver: le
          traefik.ingress.kubernetes.io/router.middlewares: control-panel-chainstack-api-strip@kubernetescrd
      spec:
        ingressClassName: traefik
        rules:
          - host: "{{ item }}"
            http:
              paths:
                - path: /
                  pathType: Prefix
                  backend:
                    service:
                      name: cp-cp-ui
                      port:
                        number: 80
        tls:
          - hosts:
              - "{{ item }}"
  loop: "{{ chainstack_ingress_domains }}"
  loop_control:
    label: "{{ item }}"

- name: Patch Chainstack UI ConfigMap apiBaseUrl
  kubernetes.core.k8s:
    state: patched
    kubeconfig: /etc/rancher/k3s/k3s.yaml
    definition:
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: cp-cp-ui-config
        namespace: control-panel
      data:
        config.json: |
          {"apiBaseUrl":"/api"}
  register: ui_config_patch

- name: Restart Chainstack UI deployment
  kubernetes.core.k8s:
    state: patched
    kubeconfig: /etc/rancher/k3s/k3s.yaml
    definition:
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: cp-cp-ui
        namespace: control-panel
      spec:
        template:
          metadata:
            annotations:
              kubectl.kubernetes.io/restartedAt: "{{ ansible_date_time.iso8601 }}"
  when: ui_config_patch.changed

tasks/password.yml (Placeholder)

Note: The original snippet referenced password.yml but didn't provide the content. This is a standard implementation to extract secrets.

- name: Save Chainstack credentials to file
  ansible.builtin.shell: |
    kubectl get secret cp-postgresql-ha-postgresql -n {{ chainstack_namespace }} -o jsonpath='{.data.password}' | base64 -d > /tmp/chainstack_password.txt
    echo "Password saved to /tmp/chainstack_password.txt"
  args:
    creates: /tmp/chainstack_password.txt

3. Template File

templates/99-chainstack.conf.j2

# Chainstack node kernel tuning
# Applied via sysctl --system

{% for key, value in chainstack_sysctl_params.items() %}
{{ key }}={{ value }}
{% endfor %}

4. Required Variables File (group_vars/all.yml or vars.yml)

You need to define the chainstack_sysctl_params variable used in the template.

chainstack_sysctl_params:
  net.core.rmem_max: 16777216
  net.core.wmem_max: 16777216
  net.ipv4.tcp_rmem: "4096 87380 16777216"
  net.ipv4.tcp_wmem: "4096 65536 16777216"
  net.ipv4.tcp_congestion_control: bbr
  net.ipv4.tcp_moderate_rcvbuf: 1
  net.core.netdev_max_backlog: 5000
  net.ipv4.tcp_max_syn_backlog: 8192
  net.ipv4.tcp_max_tw_buckets: 1440000
  net.ipv4.tcp_tw_reuse: 1
  net.ipv4.tcp_fin_timeout: 15
  net.ipv4.tcp_keepalive_time: 1200
  net.ipv4.tcp_keepalive_probes: 5
  net.ipv4.tcp_keepalive_intvl: 15
  vm.swappiness: 10
  vm.vfs_cache_pressure: 50

How to Run

  1. Prepare the environment: Ensure cpctl.sh and files/chainstack-node-policy.yaml exist in your playbook directory.
  2. Edit variables: Modify site.yml or create a group_vars/all.yml to set your domain, email, and version.
  3. Execute:
    ansible-playbook -i inventory.ini site.yml
    
    (Where inventory.ini contains your target Ubuntu server IP).
question_mark
Is there anything I can help you with?
question_mark
AI Assistant ×