diff --git a/ansible/dpf/README.md b/ansible/dpf/README.md new file mode 100644 index 0000000..e85973c --- /dev/null +++ b/ansible/dpf/README.md @@ -0,0 +1,20 @@ +These are some sample Ansible playbooks to setup a Db2 DPF instance on a set of VMs. + +The Db2 DPF instance uses NFS for the shared instance home directory. + +The playbooks assume your storage is also virtualized. +Meaning a VM exists that provides storage (via iSCSI) to the VMs running Db2. + +See the [vars.yaml](playbooks/vars.yaml) file for how you can specify things like the IP +addresses of the VMs, the name of the Db2 installation image, the number of MLNs per VM +and more. + +With VMs created, the following commands would run the playbooks to setup the DPF instance: + +```shell +ansible-playbook -i inventory.ini playbooks/vm_setup.yaml +ansible-playbook -i inventory.ini playbooks/storage_host_setup.yaml +ansible-playbook -i inventory.ini playbooks/storage_client_setup.yaml +ansible-playbook -i inventory.ini playbooks/db2_dpf_setup.yaml +ansible-playbook -i inventory.ini playbooks/db2_storage_setup.yaml +``` diff --git a/ansible/dpf/inventory.ini b/ansible/dpf/inventory.ini new file mode 100644 index 0000000..d22c9cf --- /dev/null +++ b/ansible/dpf/inventory.ini @@ -0,0 +1,27 @@ +# All hosts that can run Db2 +[db2_hosts] +dbhost1 +dbhost2 +dbhost3 +dbhost4 +dbhost5 +dbhost6 +dbhost7 +dbhost8 +dbhost9 +dbhost10 +dbhost11 +dbhost12 +dbhost13 +dbhost14 +dbhost15 +dbhost16 +dbhost17 +dbhost18 +dbhost19 +dbhost20 + +# Only used if using a VM as a storage server. +# If you have a real SAN, or access your storage some other way, you don't need this. +[storage_host] +dbhost-storage diff --git a/ansible/dpf/playbooks/db2_dpf_setup.yaml b/ansible/dpf/playbooks/db2_dpf_setup.yaml new file mode 100644 index 0000000..f9b6eb8 --- /dev/null +++ b/ansible/dpf/playbooks/db2_dpf_setup.yaml @@ -0,0 +1,137 @@ +--- +- hosts: db2_hosts + any_errors_fatal: true + gather_facts: true + vars_files: + - vars.yaml + + tasks: + + - name: Run play to install Db2 + include_tasks: db2_install.yaml + + - name: Run play to setup shared home on NFS server + when: inventory_hostname == groups['db2_hosts'][0] + include_tasks: nfs_server_shared_home.yaml + + - name: Run play to setup shared home on NFS clients + include_tasks: nfs_client_shared_home.yaml + + - name: Copy private ssh key for root to hosts + copy: + src: root_id_rsa + dest: /root/.ssh/id_rsa + mode: '0400' + + - name: Copy public ssh key for root to hosts + copy: + src: root_id_rsa.pub + dest: /root/.ssh/id_rsa.pub + mode: '0400' + + - name: Update authorized_keys for root + authorized_key: + user: root + key: "{{ lookup('file', 'root_id_rsa.pub') }}" + + - name: Update ssh_known_hosts + blockinfile: + path: /etc/ssh/ssh_known_hosts + create: true + insertafter: EOF + block: | + {% for host in groups['db2_hosts'] %} + {{ hostvars[host]['ansible_default_ipv4']['address'] }} {{ 'ssh-rsa' }} {{hostvars[host]['ansible_ssh_host_key_rsa_public'] }} + {{ hostvars[host]['ansible_hostname'] }} {{ 'ssh-rsa' }} {{hostvars[host]['ansible_ssh_host_key_rsa_public'] }} + {% endfor %} + + - name: Create Db2 Instance + when: inventory_hostname == groups['db2_hosts'][0] + command: + cmd: /opt/ibm/db2/instance/db2icrt -u db2fenc1 db2inst1 + creates: /home/db2inst1/sqllib + + - name: Create .ssh dir for db2inst1 + when: inventory_hostname == groups['db2_hosts'][0] + file: + path: /home/db2inst1/.ssh + state: directory + mode: '0700' + owner: db2inst1 + group: db2inst1 + + - name: Copy private ssh key for db2inst1 to hosts + when: inventory_hostname == groups['db2_hosts'][0] + copy: + src: db2inst1_id_rsa + dest: /home/db2inst1/.ssh/id_rsa + mode: '0400' + owner: db2inst1 + group: db2inst1 + + - name: Copy public ssh key for db2inst1 to hosts + when: inventory_hostname == groups['db2_hosts'][0] + copy: + src: db2inst1_id_rsa.pub + dest: /home/db2inst1/.ssh/id_rsa.pub + mode: '0400' + owner: db2inst1 + group: db2inst1 + + - name: Update authorized_keys for db2inst1 + when: inventory_hostname == groups['db2_hosts'][0] + become_user: db2inst1 + authorized_key: + user: db2inst1 + key: "{{ lookup('file', 'db2inst1_id_rsa.pub') }}" + + - name: Set SELinux boolean to allow passwordless ssh when home is on NFS + seboolean: + name: use_nfs_home_dirs + state: true + persistent: true + + - name: Create db2nodes.cfg + when: inventory_hostname == groups['db2_hosts'][0] + copy: + dest: /home/db2inst1/sqllib/db2nodes.cfg + content: | + {% for item in groups['db2_hosts'] | product(range(mlns_per_host)) %} + {{ loop.index - 1 }} {{ hostvars[item[0]]['ansible_hostname'] }} {{ item[1] }} + {% endfor %} + owner: db2inst1 + group: db2inst1 + + - name: Remove entries from /etc/services + lineinfile: + path: /etc/services + search_string: 'db2inst1' + state: absent + + # TODO Should use loop over mlns_per_host below + - name: Add entries to /etc/services + blockinfile: + path: /etc/services + insertafter: EOF + block: | + DB2_db2inst1 20016/tcp + DB2_db2inst1_1 20017/tcp + DB2_db2inst1_2 20018/tcp + DB2_db2inst1_3 20019/tcp + DB2_db2inst1_4 20020/tcp + DB2_db2inst1_5 20021/tcp + DB2_db2inst1_6 20022/tcp + DB2_db2inst1_7 20023/tcp + DB2_db2inst1_8 20024/tcp + DB2_db2inst1_END 20025/tcp + db2c_db2inst1 25010/tcp + + # Need to open all non-privileged ports as per https://www.ibm.com/docs/en/db2/12.1.0?topic=support-packet-filter-firewalls + # Here we just add all our hosts to the trusted zone + - name: Add firewall rule for Db2 + firewalld: + source: "{{ vm_ip_prefix }}.0/24" + zone: trusted + state: enabled + permanent: yes + immediate: yes diff --git a/ansible/dpf/playbooks/db2_install.yaml b/ansible/dpf/playbooks/db2_install.yaml new file mode 100644 index 0000000..5f1f594 --- /dev/null +++ b/ansible/dpf/playbooks/db2_install.yaml @@ -0,0 +1,40 @@ +- name: Check for Db2 Install + stat: + path: /opt/ibm/db2 + register: db2_install_path + +- name: Upload Db2 Image + unarchive: + src: "{{ db2_install_image }}" + dest: /root + creates: /root/server + when: db2_install_path.stat.exists == False + +# For pacemaker +- name: Install Db2 Prereqs + dnf: + name: + - ksh + - python3-dnf-plugin-versionlock + state: present + +- name: Install Db2 + command: + cmd: /root/server/db2_install -n -y -p SERVER -b /opt/ibm/db2 + creates: /opt/ibm/db2 + +- name: Remove installer files + file: + path: /root/server + state: absent + +- name: Create db2inst1 user + user: + name: db2inst1 + uid: 5000 + create_home: false + +- name: Create db2fenc1 user + user: + name: db2fenc1 + uid: 5001 diff --git a/ansible/dpf/playbooks/db2_storage_setup.yaml b/ansible/dpf/playbooks/db2_storage_setup.yaml new file mode 100644 index 0000000..d7aa164 --- /dev/null +++ b/ansible/dpf/playbooks/db2_storage_setup.yaml @@ -0,0 +1,60 @@ +--- +- hosts: db2_hosts + any_errors_fatal: true + gather_facts: true + vars_files: + - vars.yaml + + tasks: + + - name: Create filesystem on data device + filesystem: + dev: "{{ '/dev/' }}{{ item.key }}" + fstype: xfs + opts: "{{ '-L DATA%04d' % item.value.model.split('_')[1] | int }}" + loop: "{{ ansible_devices|dict2items }}" + loop_control: + label: "{{ item.value.model }}" + when: "inventory_hostname == groups['db2_hosts'][0] and item.value.model != None and item.value.model.startswith('DB2DATA_')" + + # This is not sufficient to pickup new labels, so we run udevadm below + - name: Rescan iSCSI devices to pickup LABELs + open_iscsi: + rescan: true + + - name: Run command to read the new fs labels + command: udevadm trigger + + - name: Update facts for devices so LABEL data is avaialble after filesystem create + setup: + gather_subset: devices + + - name: Add data device filesystems to fstab + mount: + path: "{{ db2data_path }}/db2inst1/{{ item.value.links.labels[0] | replace('DATA', 'NODE') }}" + src: "{{ 'LABEL=' }}{{ item.value.links.labels[0] }}" + fstype: xfs + opts: defaults,noauto + state: present + loop: "{{ ansible_devices|dict2items }}" + loop_control: + label: "{{ item.value.model }}" + when: "item.value.model != None and item.value.model.startswith('DB2DATA_')" + + - name: Mount needed data device filesystems for each host + mount: + path: "{{ db2data_path }}/db2inst1/{{ 'NODE%04d' % i }}" + src: "{{ 'LABEL=' }}{{ 'DATA%04d' % i }}" + state: mounted + fstype: xfs + loop: "{{ groups['db2_hosts'] | product(range(mlns_per_host)) }}" + loop_control: + index_var: i + when: "item[0] == inventory_hostname" + + - name: Set permissions on db2data path + file: + path: "{{ db2data_path }}" + owner: db2inst1 + group: db2inst1 + recurse: true diff --git a/ansible/dpf/playbooks/db2_uninstall.yaml b/ansible/dpf/playbooks/db2_uninstall.yaml new file mode 100644 index 0000000..432594b --- /dev/null +++ b/ansible/dpf/playbooks/db2_uninstall.yaml @@ -0,0 +1,20 @@ +--- +- hosts: db2_hosts + any_errors_fatal: true + gather_facts: false + vars_files: + - vars.yaml + + tasks: + + - name: Drop Db2 Instance + when: inventory_hostname == groups['db2_hosts'][0] + command: + cmd: /opt/ibm/db2/instance/db2idrop db2inst1 + removes: /home/db2inst1/sqllib + + - name: Uninstall Db2 + command: + cmd: /opt/ibm/db2/install/db2_deinstall -a + removes: /opt/ibm/db2 + diff --git a/ansible/dpf/playbooks/nfs_client_shared_home.yaml b/ansible/dpf/playbooks/nfs_client_shared_home.yaml new file mode 100644 index 0000000..dec6c7e --- /dev/null +++ b/ansible/dpf/playbooks/nfs_client_shared_home.yaml @@ -0,0 +1,12 @@ +- name: Install NFS package + dnf: + name: nfs-utils + state: latest + +- name: Mount shared home + mount: + path: /home/db2inst1 + src: "{{ groups['db2_hosts'][0] }}{{':'}}{{ shared_home_export }}" + fstype: nfs + opts: rw,sync,hard,nofail + state: mounted diff --git a/ansible/dpf/playbooks/nfs_server_shared_home.yaml b/ansible/dpf/playbooks/nfs_server_shared_home.yaml new file mode 100644 index 0000000..92495b2 --- /dev/null +++ b/ansible/dpf/playbooks/nfs_server_shared_home.yaml @@ -0,0 +1,68 @@ +- name: Install NFS package + dnf: + name: nfs-utils + state: latest + +- name: Stop NFS server + systemd: + name: nfs-server + state: stopped + +- name: Create filesystem on shared home + filesystem: + dev: "{{ shared_home_dev }}" + fstype: xfs + opts: "-L SHARED_HOME" + +- name: Mount shared home export + mount: + path: "{{ shared_home_export }}" + src: "{{ shared_home_dev }}" + fstype: xfs + opts: defaults,nofail + state: mounted + +# XXX db2inst1 should be created by this point +- name: Set permissions on shared home export + file: + path: "{{ shared_home_export }}" + owner: db2inst1 + group: db2inst1 + +- name: Initialize db2inst1 home directory + copy: + src: /etc/skel/ + dest: /home/db2inst1 + force: false + owner: db2inst1 + group: db2inst1 + +- name: Generate ssh key for db2inst1 + user: + name: db2inst1 + uid: 5000 + create_home: false + generate_ssh_key: true + +- name: Setup /etc/exports + blockinfile: + path: /etc/exports + create: true + insertafter: EOF + block: | + {% for host in groups['db2_hosts'] %} + {{ shared_home_export }} {{ host }}{{ '(rw,no_root_squash)' }} + {% endfor %} + +- name: Add firewall rule for NFS server + firewalld: + state: enabled + service: nfs + permanent: yes + immediate: yes + +- name: Setup NFS server + systemd: + name: nfs-server + state: restarted + enabled: false diff --git a/ansible/dpf/playbooks/storage_client_setup.yaml b/ansible/dpf/playbooks/storage_client_setup.yaml new file mode 100644 index 0000000..08b06dc --- /dev/null +++ b/ansible/dpf/playbooks/storage_client_setup.yaml @@ -0,0 +1,36 @@ +--- +- hosts: db2_hosts + any_errors_fatal: true + gather_facts: false + vars_files: + - vars.yaml + + tasks: + + - name: Install packages + dnf: + name: iscsi-initiator-utils + state: latest + + - name: Perform discovery + open_iscsi: + discover: true + ip: "{{ groups['storage_host'][0] }}" + +# Fails on first run of new host - need to discover first? +# Do we need this? Commenting out for now. + - name: Logout + open_iscsi: + login: false + node_auth: "None" + ip: "{{ groups['storage_host'][0] }}" + + - name: Login and discover iSCSI targets + open_iscsi: + login: true + node_auth: "None" + ip: "{{ groups['storage_host'][0] }}" + + - name: Rescan + open_iscsi: + rescan: true diff --git a/ansible/dpf/playbooks/storage_host_setup.yaml b/ansible/dpf/playbooks/storage_host_setup.yaml new file mode 100644 index 0000000..5b47644 --- /dev/null +++ b/ansible/dpf/playbooks/storage_host_setup.yaml @@ -0,0 +1,92 @@ +--- +- hosts: storage_host + gather_facts: true + vars_files: + - vars.yaml + + tasks: + + - name: Install packages + dnf: + name: targetcli + state: latest + + # Stop it (if already running) to avoid device in use errors + - name: Stop target service + systemd: + name: target + state: stopped + + - name: Create shared home vg + lvg: + vg: shared_home_vg + pvs: /dev/vdb + + - name: Create shared home lv + lvol: + vg: shared_home_vg + lv: shared_home_lv + size: 100%FREE + shrink: false + + - name: Create data volume group + lvg: + vg: storagevg + pvs: /dev/vdc + + - name: Create data logical volumes + lvol: + vg: storagevg + lv: "{{ 'datalv_' }}{{ item }}" + size: 5g + loop: "{{ range(groups['db2_hosts'] | product(range(mlns_per_host)) | length)}}" + + - name: Start target service + systemd: + name: target + state: started + enabled: true + + - name: Open target port on firewall + firewalld: + state: enabled + service: iscsi-target + permanent: yes + immediate: yes + + - name: Create iSCSI target + vars: + - iqn: iqn.2025-04.vm.storage:target + - shared_home_lun_name: shared_home_lun + + shell: | + targetcli <