diff --git a/kubeblocks.yml b/kubeblocks.yml index 97d5471..ad9dd5f 100755 --- a/kubeblocks.yml +++ b/kubeblocks.yml @@ -1,19 +1,23 @@ #!/usr/bin/env ansible-playbook --- -# ~/.kube/config should be valid -- name: Manage KubeBlocks +- name: Manage kbcli hosts: controller become: true + tasks: + - name: Configure KubeBlocks + ansible.builtin.include_role: + name: clusterlust.kbcli + +- name: Manage KubeBlocks + hosts: localhost + become: false + connection: local vars: # Semaphore does not pass $HOME so K8S_AUTH_KUBECONFIG needs to be set. k8s_auth_kubeconfig: /home/semaphore/.kube/config - # To use KubeBlocks addons set this Boolean - #use_kb_addons: true + # To manage KubeBlocks addons set this Boolean + # use_kb_addons: true tasks: - - name: Install KubeBlocks - ansible.builtin.include_role: - name: bbaassssiiee.kubeblocks - - name: Configure KubeBlocks ansible.builtin.include_role: name: clusterlust.kubeblocks diff --git a/prepare.sh b/prepare.sh index d653462..fd47980 100755 --- a/prepare.sh +++ b/prepare.sh @@ -1,3 +1,2 @@ #!/bin/bash -ansible-galaxy install -r roles/requirements.yml -ansible-galaxy collection install -r collections/requirements.yml +ansible-galaxy collection download -r collections/requirements.yml diff --git a/roles/clusterlust.kbcli/defaults/main.yml b/roles/clusterlust.kbcli/defaults/main.yml new file mode 100644 index 0000000..1147aa7 --- /dev/null +++ b/roles/clusterlust.kbcli/defaults/main.yml @@ -0,0 +1,5 @@ +# kbcli +kbcli_releases_url: 'https://api.github.com/repos/apecloud/kbcli/releases/latest' +kbcli_repo_url: 'https://github.com/apecloud/kbcli/releases/download' +use_kbcli: true +desired_state: present diff --git a/roles/clusterlust.kbcli/tasks/absent.yml b/roles/clusterlust.kbcli/tasks/absent.yml new file mode 100644 index 0000000..fa345df --- /dev/null +++ b/roles/clusterlust.kbcli/tasks/absent.yml @@ -0,0 +1,7 @@ +--- + +- name: Remove kbcli + when: use_kbcli | bool + ansible.builtin.package: + name: kbcli + state: absent diff --git a/roles/clusterlust.kbcli/tasks/kbcli.yml b/roles/clusterlust.kbcli/tasks/kbcli.yml new file mode 100644 index 0000000..b8c2f08 --- /dev/null +++ b/roles/clusterlust.kbcli/tasks/kbcli.yml @@ -0,0 +1,65 @@ +--- +- name: Find kbcli release + when: kbcli_download_url is not defined + become: false + block: + + - name: Get kbcli Release info from Github + ansible.builtin.uri: + url: "{{ kbcli_releases_url }}" + return_content: true + body_format: json + register: _sem_rel + check_mode: false + + - name: Set kbcli requirements + ansible.builtin.set_fact: + kbcli_version: "{{ _sem_rel['json']['tag_name'][1:] }}" + kbcli_pkg_ext: "{{ (ansible_facts['os_family'] == 'RedHat') | ternary('rpm', 'deb') }}" + kbcli_pkg_arch: "{{ (ansible_facts['architecture'] == 'x86_64') | ternary('amd64', 'arm64') }}" + + - name: Set kbcli_download_url + ansible.builtin.set_fact: + kbcli_download_url: "{{ kbcli_repo_url }}/{{ _sem_rel.json.name }}/kbcli-linux-{{ kbcli_pkg_arch }}-v{{ kbcli_version }}.{{ kbcli_pkg_ext }}" + +- name: Install kbcli package + when: kbcli_download_package | bool + block: + + - name: Download kbcli + become: false + ansible.builtin.get_url: + url: "{{ kbcli_download_url }}" + dest: "/tmp/kbcli.{{ kbcli_pkg_ext }}" + mode: '0644' + register: downloaded_kbcli + + # This task will be skipped in check mode + - name: Ensure kbcli installed + become: true + ansible.builtin.dnf: + name: "file:///tmp/kbcli.{{ kbcli_pkg_ext }}" + disable_gpg_check: true + state: present + when: + - not ansible_check_mode + - downloaded_kbcli is success + - ansible_facts['os_family'] == 'RedHat' + + # This task will be skipped in check mode + - name: Ensure kbcli installed + become: true + ansible.builtin.apt: + deb: "file:///tmp/kbcli.{{ kbcli_pkg_ext }}" + allow_unauthenticated: true + state: present + when: + - not ansible_check_mode + - downloaded_kbcli is success + - ansible_facts['os_family'] == 'Debian' + + always: + - name: Remove package from /tmp + ansible.builtin.file: + path: "/tmp/kbcli.{{ kbcli_pkg_ext }}" + state: absent diff --git a/roles/clusterlust.kbcli/tasks/main.yml b/roles/clusterlust.kbcli/tasks/main.yml new file mode 100644 index 0000000..dfeedbb --- /dev/null +++ b/roles/clusterlust.kbcli/tasks/main.yml @@ -0,0 +1,4 @@ +--- +# desired_state in ['absent', 'present'] +- name: "Converge state - {{ desired_state }}" + ansible.builtin.include_tasks: "{{ desired_state }}.yml" diff --git a/roles/clusterlust.kbcli/tasks/present.yml b/roles/clusterlust.kbcli/tasks/present.yml new file mode 100644 index 0000000..4ff7590 --- /dev/null +++ b/roles/clusterlust.kbcli/tasks/present.yml @@ -0,0 +1,5 @@ +--- + +- name: Install kbcli + when: use_kbcli | bool + ansible.builtin.include_tasks: "kbcli.yml" diff --git a/roles/clusterlust.kubeblocks/README.md b/roles/clusterlust.kubeblocks/README.md new file mode 100644 index 0000000..2719eb0 --- /dev/null +++ b/roles/clusterlust.kubeblocks/README.md @@ -0,0 +1,39 @@ +kubeblocks +========== + +A role to install kbcli to work with KubeBlocks + +https://kubeblocks.io + +Run Databases on Kubernetes? Run with KubeBlocks. + +KubeBlocks is crafted for managing databases on Kubernetes, designed by domain experts with decades of experience. +It supports a wide range of stateful workloads, including relational databases, NoSQL, message queues. +By streamlining operations, enhancing flexibility, and offering extensions, KubeBlocks makes database management easier in cloud-native environments. + +Requirements +------------ + +EL or Ubuntu Linux. + +Role Variables +-------------- + +```yml +# defaults +desired_state: present # or absent +``` + +Dependencies +------------ +None. + +Example Playbook +---------------- + + +```yaml + - hosts: controller + roles: + - role: clusterlust.kubeblocks +``` diff --git a/roles/clusterlust.kubeblocks/defaults/main.yml b/roles/clusterlust.kubeblocks/defaults/main.yml index a26248a..d71c675 100644 --- a/roles/clusterlust.kubeblocks/defaults/main.yml +++ b/roles/clusterlust.kubeblocks/defaults/main.yml @@ -1,9 +1,62 @@ --- -# no-op -kubeblocks_clusters: [] -# kubeblocks_clusters: -# - kb_cluster_name: database -# kb_cluster_type: postgresql -# kb_cluster_version: postgresql-16.4.0 -# options: '--cpu=0.5 --storage=10 --mode=replication' -# kb_cluster_ns: postgresql +# kbcli +kbcli_releases_url: 'https://api.github.com/repos/apecloud/kbcli/releases/latest' +kbcli_repo_url: 'https://github.com/apecloud/kbcli/releases/download' +use_kbcli: true +# set true when k8s is up +use_kubeblocks: false +desired_state: present +verify_state: true +kbcli_download_package: true +kbcli_path: /usr/local/bin +# Semaphore does not pass $HOME so path might need to be more explicit. +k8s_auth_kubeconfig: "{{ lookup('env', 'HOME') }}/.kube/config" +# KubeBlocks +crd_url: 'https://github.com/apecloud/kubeblocks/releases/download/v0.9.2/kubeblocks_crds.yaml' +helm_repo_url: 'https://apecloud.github.io/helm-charts' +helm_binary: /usr/local/bin/helm +# KubeBlocks configuration depends on this Boolean: +use_kb_addons: false + +# state: absent removes addons, TODO disabling/enabling +kubeblocks_add_ons: + - addon_name: apecloud-mysql + state: absent + enabled: false + version: 0.9.0 + - addon_name: aws-load-balancer-controller + state: absent + enabled: false + version: 1.4.8 + - addon_name: kafka + state: present + enabled: true + version: 0.9.0 + - addon_name: llm + state: present + enabled: false + version: 0.9.0 + - addon_name: mongodb + state: absent + enabled: false + version: 0.9.1 + - addon_name: mysql + state: absent + enabled: false + version: 0.9.3 + - addon_name: postgresql + state: present + enabled: true + version: 0.9.0 + - addon_name: pulsar + state: present + enabled: false + version: 0.9.1 + - addon_name: qdrant + state: present + enabled: false + version: 0.9.1 + - addon_name: redis + state: present + enabled: true + version: 0.9.0 diff --git a/roles/clusterlust.kubeblocks/files/.gitkeep b/roles/clusterlust.kubeblocks/files/.gitkeep new file mode 100644 index 0000000..1592286 --- /dev/null +++ b/roles/clusterlust.kubeblocks/files/.gitkeep @@ -0,0 +1 @@ +generated files are kept here. diff --git a/roles/clusterlust.kubeblocks/tasks/absent.yml b/roles/clusterlust.kubeblocks/tasks/absent.yml new file mode 100644 index 0000000..1aa73c4 --- /dev/null +++ b/roles/clusterlust.kubeblocks/tasks/absent.yml @@ -0,0 +1,5 @@ +--- +# when: desired_state == 'absent' +- name: Remove KubeBlocks + when: use_kubeblocks | bool + ansible.builtin.include_tasks: "kubeblocks.yml" diff --git a/roles/clusterlust.kubeblocks/tasks/addons.yml b/roles/clusterlust.kubeblocks/tasks/addons.yml new file mode 100644 index 0000000..a60d592 --- /dev/null +++ b/roles/clusterlust.kubeblocks/tasks/addons.yml @@ -0,0 +1,38 @@ +- name: "Ensure add-on state present: {{ item.addon_name }}" + when: item.state == 'present' + ansible.builtin.command: "kbcli --kubeconfig={{ k8s_auth_kubeconfig }} addon install {{ item.addon_name }} --version {{ item.version }}" + register: addon_state + changed_when: "'AlreadyExists' not in addon_state.stderr" + failed_when: + - addon_state.rc != '0' + - "'AlreadyExists' not in addon_state.stderr" + - "'installed successfully' not in addon_state.stdout" + ignore_errors: true + +- name: "Ensure add-on enablement: {{ item.addon_name }}." + when: + - item.state == 'present' + - not addon_state.changed | bool + ansible.builtin.command: "kbcli --kubeconfig={{ k8s_auth_kubeconfig }} addon {{ 'enable' if item.enabled else 'disable' }} {{ item.addon_name }}" + register: addon_enablement + failed_when: + - addon_enablement.rc != '0' + - "'enabled' not in addon_enablement.stdout" + - "'disabled' not in addon_enablement.stdout" + - "'already disabled' not in addon_enablement.stdout" + - "'no change' not in addon_enablement.stdout" + - "'not being updated' not in addon_enablement.stdout" + changed_when: + - "'enabled' in addon_enablement.stdout" + - "'disabled' in addon_enablement.stdout" + - "'already disabled' not in addon_enablement.stdout" + - "'no change' not in addon_enablement.stdout" + - "'not being updated' not in addon_enablement.stdout" + +- name: "Ensure add-on state absent: {{ item.addon_name }}" + when: item.state == 'absent' + ansible.builtin.command: "kbcli --kubeconfig={{ k8s_auth_kubeconfig }} addon uninstall {{ item.addon_name }}" + register: item_state + failed_when: false + ignore_errors: true + changed_when: false diff --git a/roles/clusterlust.kubeblocks/tasks/clusters.yml b/roles/clusterlust.kubeblocks/tasks/clusters.yml deleted file mode 100644 index d26f6d8..0000000 --- a/roles/clusterlust.kubeblocks/tasks/clusters.yml +++ /dev/null @@ -1,12 +0,0 @@ -- name: "Create namespace {{ item.kb_cluster_ns }}" - kubernetes.core.k8s: - name: "{{ item.kb_cluster_ns }}" - api_version: v1 - kind: Namespace - state: present - -- name: "Create {{ item.kb_cluster_type }} cluster {{ item.kb_cluster_name }}" - environment: - K8S_AUTH_KUBECONFIG: "{{ k8s_auth_kubeconfig }}" - ansible.builtin.command: - cmd: "kbcli cluster create {{ item.kb_cluster_type }} --version={{ item.kb_cluster_version }} {{ item.options }} {{ item.kb_cluster_name }} -n {{ item.kb_cluster_ns }}" diff --git a/roles/clusterlust.kubeblocks/tasks/kubeblocks.yml b/roles/clusterlust.kubeblocks/tasks/kubeblocks.yml new file mode 100644 index 0000000..4018f57 --- /dev/null +++ b/roles/clusterlust.kubeblocks/tasks/kubeblocks.yml @@ -0,0 +1,70 @@ +--- +# ~/.kube/config should be valid + +- name: Configure KubeBlocks + when: desired_state == 'present' + block: + - name: Create temp dir + ansible.builtin.tempfile: + state: directory + suffix: temp + register: tempdir + + - name: Download the manifest + ansible.builtin.get_url: + url: "{{ crd_url }}" + dest: "{{ tempdir.path }}/{{ crd_manifest }}" + mode: '0664' + + - name: Create dependent CRDs + kubernetes.core.k8s: + state: present + src: "{{ tempdir.path }}/{{ crd_manifest }}" + namespace: kube-system + + - name: Remove the temp dir + when: tempdir.path is defined + ansible.builtin.file: + path: "{{ tempdir.path }}" + state: absent + + - name: Add the KubeBlocks Helm repository + kubernetes.core.helm_repository: + binary_path: "{{ helm_binary }}" + name: "{{ helm_repo_name }}" + url: "{{ helm_repo_url }}" + state: present + +- name: Remove KubeBlocks + when: desired_state == 'absent' + ansible.builtin.command: > + kbcli --kubeconfig={{ k8s_auth_kubeconfig }} kubeblocks uninstall -n kb-system + --auto-approve=true + changed_when: true + +- name: Manage the Helm chart + kubernetes.core.helm: + binary_path: "{{ helm_binary }}" + name: "{{ helm_chart_name }}" + chart_ref: "{{ helm_repo_name }}/{{ helm_chart_name }}" + release_namespace: "{{ namespace }}" + create_namespace: true + state: "{{ desired_state }}" + wait: true + +- name: Remove KubeBlocks Helm repository + when: desired_state == 'absent' + kubernetes.core.helm_repository: + binary_path: "{{ helm_binary }}" + name: "{{ helm_repo_name }}" + state: absent + +- name: Remove KubeBlocks crds + when: desired_state == 'absent' + ansible.builtin.shell: | + set -o pipefail; + kubectl --kubeconfig={{ k8s_auth_kubeconfig }} get crd -o name \ + | grep kubeblocks.io \ + | xargs kubectl --kubeconfig={{ k8s_auth_kubeconfig }} delete + changed_when: true + failed_when: false # for example rc 123 is empty list as inputs diff --git a/roles/clusterlust.kubeblocks/tasks/main.yml b/roles/clusterlust.kubeblocks/tasks/main.yml index 8a748e8..1deef0b 100644 --- a/roles/clusterlust.kubeblocks/tasks/main.yml +++ b/roles/clusterlust.kubeblocks/tasks/main.yml @@ -1,6 +1,8 @@ --- -- name: Install KubeBlock clusters - when: - - kubeblocks_clusters|length >= 1 - ansible.builtin.include_tasks: clusters.yml - loop: "{{ kubeblocks_clusters }}" +# desired_state in ['absent', 'present'] +- name: "Converge state - {{ desired_state }}" + ansible.builtin.include_tasks: "{{ desired_state }}.yml" + +- name: "Verify state - {{ desired_state }}" + ansible.builtin.include_tasks: verify.yml + when: verify_state | bool diff --git a/roles/clusterlust.kubeblocks/tasks/present.yml b/roles/clusterlust.kubeblocks/tasks/present.yml new file mode 100644 index 0000000..f227ce1 --- /dev/null +++ b/roles/clusterlust.kubeblocks/tasks/present.yml @@ -0,0 +1,10 @@ +--- +# when: desired_state == 'present' +- name: Install KubeBlocks + when: use_kubeblocks | bool + ansible.builtin.include_tasks: "kubeblocks.yml" + +- name: Manage KubeBlocks Add-ons + when: use_kb_addons | bool + ansible.builtin.include_tasks: "addons.yml" + loop: "{{ kubeblocks_add_ons }}" diff --git a/roles/clusterlust.kubeblocks/tasks/verify.yml b/roles/clusterlust.kubeblocks/tasks/verify.yml new file mode 100644 index 0000000..5044474 --- /dev/null +++ b/roles/clusterlust.kubeblocks/tasks/verify.yml @@ -0,0 +1,14 @@ +# Verify desired state, both absent and present can be verified. +--- +- name: "State variables - {{ desired_state }}" + ansible.builtin.include_vars: "{{ desired_state }}.yml" + +- name: Gather package facts + ansible.builtin.package_facts: + no_log: true + +- name: Assert package is in desired_state + ansible.builtin.assert: + quiet: true + that: + - "'kbcli' {{ package_clause }} ansible_facts.packages" diff --git a/roles/clusterlust.kubeblocks/vars/absent.yml b/roles/clusterlust.kubeblocks/vars/absent.yml new file mode 100644 index 0000000..4a4bf25 --- /dev/null +++ b/roles/clusterlust.kubeblocks/vars/absent.yml @@ -0,0 +1,2 @@ +--- +package_clause: 'not in' diff --git a/roles/clusterlust.kubeblocks/vars/main.yml b/roles/clusterlust.kubeblocks/vars/main.yml new file mode 100644 index 0000000..d4cad3f --- /dev/null +++ b/roles/clusterlust.kubeblocks/vars/main.yml @@ -0,0 +1,5 @@ +--- +crd_manifest: kubeblocks_crds.yaml +helm_repo_name: kubeblocks +helm_chart_name: kubeblocks +namespace: kb-system diff --git a/roles/clusterlust.kubeblocks/vars/present.yml b/roles/clusterlust.kubeblocks/vars/present.yml new file mode 100644 index 0000000..5be69e1 --- /dev/null +++ b/roles/clusterlust.kubeblocks/vars/present.yml @@ -0,0 +1,2 @@ +--- +package_clause: 'in' diff --git a/roles/requirements.yml b/roles/requirements.yml deleted file mode 100644 index 984832f..0000000 --- a/roles/requirements.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -roles: - - src: bbaassssiiee.kubeblocks - version: 1.4.7