65-agent-ipi-multinode.yml 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. ---
  2. # Configure the agent installation artifacts for multi-node PXE installation.
  3. #
  4. # TODO: For fact delegation, when certain hosts are unreachable, add
  5. # "ignore_unreachable: yes" to the tasks and then handle it with a
  6. # prompt & fail afterwards.
  7. #
  8. - name: Prepare the files required for a multi-node installation using agent install.
  9. hosts: workstation.lab.example.com
  10. become: no
  11. gather_subset: min
  12. tasks:
  13. # NOTE: This one is actually a prep item, but it's only needed for agent installs.
  14. - name: Ensure nmstate is installed.
  15. become: yes
  16. ansible.builtin.yum:
  17. name: nmstate
  18. state: present
  19. - name: Check the dependency status.
  20. ansible.builtin.stat:
  21. path: "{{ ansible_facts['user_dir'] }}/{{ item }}"
  22. get_attributes: no
  23. get_checksum: no
  24. get_mime: no
  25. register: dependencies
  26. loop:
  27. - install-pull-secret
  28. - .ssh/openshift.pub
  29. - ca/ca-cert.pem
  30. - mirror/working-dir/cluster-resources/idms-oc-mirror.yaml
  31. - Downloads/rhcos-418.94.202501221327-0-live.x86_64.iso
  32. - ansible.builtin.assert:
  33. that:
  34. - dependencies.results[0].stat.exists
  35. - dependencies.results[1].stat.exists
  36. - dependencies.results[2].stat.exists
  37. - dependencies.results[3].stat.exists
  38. - dependencies.results[4].stat.exists
  39. fail_msg: |
  40. ERROR: Either pull secret, SSH keypair, CA certificate, RHCOS ISO, or mirror artifacts are missing.
  41. Ensure all the relevant preceding tasks have been completed:
  42. - Quay prerequisites,
  43. - Quay deployment,
  44. - oc-mirror prerequisites,
  45. - oc-mirror execution,
  46. - coreos-installer prerequisites
  47. Exiting.
  48. success_msg: "OK, dependencies exist."
  49. - name: Check whether someone fiddled with installation before.
  50. ansible.builtin.stat:
  51. path: "{{ ansible_facts['user_dir'] }}/ipi/.openshift_install.log"
  52. register: install_log
  53. - name: Warn if installation log was found.
  54. ansible.builtin.pause:
  55. prompt: |
  56. WARNING: Found .openshift_install.log in the cluster working directory. This usually
  57. means there were previous attempts of creating installation artifacts.
  58. If you want to recreate the cluster working directory from scratch, run this
  59. playbook with the variable "recreate_cluster_dir" set to any value like this:
  60. ansible-playbook -e recreate_cluster_dir=yes ./65-agent-ipi-multinode.yml
  61. Continuing in 5 seconds unless you interrupt execution.
  62. seconds: 5
  63. when:
  64. - install_log.stat.exists
  65. - recreate_cluster_dir is not defined
  66. - name: Load the dependencies as facts.
  67. ansible.builtin.set_fact:
  68. pull_secret: "{{ lookup('ansible.builtin.file', ansible_facts['user_dir'] + '/install-pull-secret') }}"
  69. public_key: "{{ lookup('ansible.builtin.file', ansible_facts['user_dir'] + '/.ssh/openshift.pub') }}"
  70. lab_ca_cert: "{{ lookup('ansible.builtin.file', ansible_facts['user_dir'] + '/ca/ca-cert.pem') }}"
  71. content_sources: "{{ lookup('ansible.builtin.file', ansible_facts['user_dir'] + '/mirror/working-dir/cluster-resources/idms-oc-mirror.yaml')
  72. | ansible.builtin.from_yaml_all }}"
  73. - name: Set the fact determining installation type (required for templating).
  74. ansible.builtin.set_fact:
  75. install_type: ipi
  76. install_host: master03.ocp4.example.com
  77. other_hosts:
  78. - name: master01.ocp4.example.com
  79. role: master
  80. - name: master02.ocp4.example.com
  81. role: master
  82. - name: worker01.ocp4.example.com
  83. role: worker
  84. - name: worker02.ocp4.example.com
  85. role: worker
  86. - name: Collect facts from the rendezvous machine (must be reachable for that).
  87. delegate_to: "{{ install_host }}"
  88. delegate_facts: yes
  89. ansible.builtin.setup:
  90. gather_subset: min,interfaces
  91. - name: Collect facts from the other machines (must be reachable for that).
  92. delegate_to: "{{ item.name }}"
  93. delegate_facts: yes
  94. ansible.builtin.setup:
  95. gather_subset: min,interfaces
  96. loop: "{{ other_hosts }}"
  97. - name: Ensure install-config is there.
  98. ansible.builtin.template:
  99. src: templates/install-config-template.yaml.j2
  100. dest: "{{ ansible_facts['user_dir'] }}/install-config-ipi.yaml"
  101. mode: 0644
  102. owner: student
  103. group: student
  104. register: updated_install_config
  105. - name: Ensure agent-config is there.
  106. ansible.builtin.template:
  107. src: templates/agent-config-template.yaml.j2
  108. dest: "{{ ansible_facts['user_dir'] }}/agent-config-ipi.yaml"
  109. mode: 0644
  110. owner: student
  111. group: student
  112. register: updated_agent_config
  113. - name: Remove the installation directory if so required.
  114. ansible.builtin.file:
  115. path: "{{ ansible_facts['user_dir'] }}/ipi"
  116. state: absent
  117. when:
  118. - recreate_cluster_dir is defined
  119. - recreate_cluster_dir
  120. - name: Ensure the presence of installation directory.
  121. ansible.builtin.file:
  122. path: "{{ ansible_facts['user_dir'] }}/ipi"
  123. state: directory
  124. mode: 0755
  125. - name: Also, ensure that the right install-config.yaml file is in there.
  126. ansible.builtin.copy:
  127. src: "{{ ansible_facts['user_dir'] }}/install-config-ipi.yaml"
  128. remote_src: yes
  129. dest: "{{ ansible_facts['user_dir'] }}/ipi/install-config.yaml"
  130. mode: 0644
  131. register: published_install_config
  132. when:
  133. - (not install_log.stat.exists) or (recreate_cluster_dir is defined) or updated_install_config.changed or updated_agent_config.changed
  134. - name: The same, but for agent-config.yaml.
  135. ansible.builtin.copy:
  136. src: "{{ ansible_facts['user_dir'] }}/agent-config-ipi.yaml"
  137. remote_src: yes
  138. dest: "{{ ansible_facts['user_dir'] }}/ipi/agent-config.yaml"
  139. mode: 0644
  140. register: published_agent_config
  141. when:
  142. - (not install_log.stat.exists) or (recreate_cluster_dir is defined) or updated_install_config.changed or updated_agent_config.changed
  143. # TODO: agent installer needs to have a disabled check of valid-platform-network-settings or it refuses to
  144. # install complaining about OpenStack not being supported. Long story short:
  145. #
  146. # https://github.com/openshift/assisted-service/blob/94614068710f5e528f56ab034988cb7d1ad42f0d/docs/user-guide/deploy-on-OSP.md
  147. # https://github.com/orgs/okd-project/discussions/2112
  148. #
  149. # I suppose there has to be a customization of /var/usrlocal/share/assisted-service/assisted-service.env,
  150. # where DISABLED_HOST_VALIDATIONS=valid-platform-network-settings
  151. #
  152. # Short of that, the rendezvous host needs to have this file patched, then:
  153. #
  154. # systemctl restart assisted-service
  155. # systemctl restart agent
  156. #
  157. # and reboot any nodes that have registered by then so they re-register.
  158. #
  159. - name: This block will only execute if install-config or agent-config files were published.
  160. block:
  161. - name: Ensure the presence of customization directory.
  162. ansible.builtin.file:
  163. path: "{{ ansible_facts['user_dir'] }}/ipi/openshift"
  164. state: directory
  165. mode: 0755
  166. - name: Render chrony customizations in home directory.
  167. ansible.builtin.template:
  168. src: templates/chrony-customization.bu.j2
  169. dest: "{{ ansible_facts['user_dir'] }}/chrony-{{ item }}.bu"
  170. mode: 0644
  171. owner: student
  172. group: student
  173. loop:
  174. - master
  175. - worker
  176. - name: Publish chrony customizations in manifests directory.
  177. ansible.builtin.command:
  178. cmd: butane ./chrony-{{ item }}.bu -o ./ipi/openshift/99_chrony_{{ item }}.yaml
  179. chdir: "{{ ansible_facts['user_dir'] }}"
  180. creates: ipi/openshift/99_chrony_{{ item }}.yaml
  181. loop:
  182. - master
  183. - worker
  184. - name: Ensure the agent image cache directory exists.
  185. ansible.builtin.file:
  186. path: "{{ ansible_facts['user_dir'] }}/.cache/agent/image_cache"
  187. state: directory
  188. mode: 0755
  189. - name: Ensure that the agent ISO and all other artifacts are gone if anything was updated.
  190. ansible.builtin.file:
  191. path: "{{ ansible_facts['user_dir'] }}/ipi/{{ item }}"
  192. state: absent
  193. loop:
  194. - auth
  195. - boot-artifacts
  196. - .openshift_install.log
  197. - .openshift_install_state.json
  198. when: published_install_config.changed or published_agent_config.changed
  199. - name: Check whether the initrd is there.
  200. ansible.builtin.stat:
  201. path: "{{ ansible_facts['user_dir'] }}/ipi/boot-artifacts/agent.x86_64-initrd.img"
  202. get_attributes: no
  203. get_checksum: no
  204. get_mime: no
  205. register: agent_initrd
  206. - name: Ensure that CoreOS ISO is a link to the downloaded one in Downloads.
  207. ansible.builtin.file:
  208. path: "{{ ansible_facts['user_dir'] }}/.cache/agent/image_cache/coreos-x86_64.iso"
  209. state: hard
  210. src: "{{ ansible_facts['user_dir'] }}/Downloads/rhcos-418.94.202501221327-0-live.x86_64.iso"
  211. - name: Create agent boot artifacts.
  212. ansible.builtin.command:
  213. cmd: openshift-install-fips agent create pxe-files
  214. chdir: "{{ ansible_facts['user_dir'] }}/ipi"
  215. when: not agent_initrd.stat.exists
  216. # Copy the boot artifacts to utility and prepare the PXE boot stuff, fix DNS bits.
  217. - name: Copy the boot artifacts to utility and prepare the PXE boot stuff.
  218. hosts: utility.lab.example.com
  219. become: no
  220. gather_subset: min
  221. tasks:
  222. - name: Set the fact containing all the installation hosts.
  223. ansible.builtin.set_fact:
  224. install_hosts:
  225. - master01.ocp4.example.com
  226. - master02.ocp4.example.com
  227. - master03.ocp4.example.com
  228. - worker01.ocp4.example.com
  229. - worker02.ocp4.example.com
  230. - name: Collect facts from the installation machines (must be reachable for that).
  231. delegate_to: "{{ item }}"
  232. delegate_facts: yes
  233. ansible.builtin.setup:
  234. gather_subset: min,interfaces
  235. loop: "{{ install_hosts }}"
  236. - name: Ensure there is a target directory for boot files on utility.
  237. become: yes
  238. ansible.builtin.file:
  239. path: /var/www/html/openshift4/ipi
  240. owner: root
  241. group: root
  242. mode: 0755
  243. state: directory
  244. - name: Copy the boot artifacts to the target directory if necessary.
  245. become: yes
  246. ansible.builtin.copy:
  247. src: "/home/student/ipi/boot-artifacts/{{ item }}"
  248. dest: "/var/www/html/openshift4/ipi/{{ item }}"
  249. owner: root
  250. group: root
  251. mode: 0644
  252. loop:
  253. - agent.x86_64-vmlinuz
  254. - agent.x86_64-initrd.img
  255. - agent.x86_64-rootfs.img
  256. - name: Ensure the IPI PXE boot config file is in TFTP boot directory.
  257. become: yes
  258. ansible.builtin.copy:
  259. dest: "/var/lib/tftpboot/pxelinux.cfg/pxeboot-ipi.cfg"
  260. owner: root
  261. group: root
  262. mode: 0644
  263. content: |
  264. default menu.c32
  265. prompt 0
  266. timeout 50
  267. menu title **** OpenShift 4.18.6 Agent PXE Boot Menu ****
  268. label Install CoreOS 4.18.6 Agent Node
  269. kernel http://192.168.50.254:8080/openshift4/ipi/agent.x86_64-vmlinuz
  270. append console=tty0 console=ttyS0 initrd=http://192.168.50.254:8080/openshift4/ipi/agent.x86_64-initrd.img coreos.live.rootfs_url=http://192.168.50.254:8080/openshift4/ipi/agent.x86_64-rootfs.img rw ignition.firstboot ignition.platform.id=metal
  271. - name: Ensure files with MAC addresses are symlinks to pxeboot-ipi.cfg
  272. become: yes
  273. ansible.builtin.file:
  274. src: pxeboot-ipi.cfg
  275. dest: "/var/lib/tftpboot/pxelinux.cfg/{{ hostvars[item]['ansible_facts']['default_ipv4']['macaddress'] | regex_replace('^', '01-') | regex_replace(':', '-') }}"
  276. owner: root
  277. group: root
  278. state: link
  279. force: yes
  280. loop: "{{ install_hosts }}"
  281. - name: Ensure forward DNS records are there.
  282. become: yes
  283. ansible.builtin.lineinfile:
  284. path: /var/named/ocp4.example.com.db
  285. regexp: "{{ item.regex }}"
  286. line: "{{ item.line }}"
  287. insertafter: "{{ item.after | default(omit) }}"
  288. insertbefore: "{{ item.before | default(omit) }}"
  289. loop:
  290. - regex: '^master01\.ipi '
  291. line: "master01.ipi IN A 192.168.50.10"
  292. after: '^master01 '
  293. - regex: '^master02\.ipi '
  294. line: "master02.ipi IN A 192.168.50.11"
  295. after: '^master02 '
  296. - regex: '^master03\.ipi '
  297. line: "master03.ipi IN A 192.168.50.12"
  298. after: '^master03 '
  299. - regex: '^worker01\.ipi '
  300. line: "worker01.ipi IN A 192.168.50.13"
  301. after: '^worker01 '
  302. - regex: '^worker02\.ipi '
  303. line: "worker02.ipi IN A 192.168.50.14"
  304. after: '^worker02 '
  305. - regex: '^api\.ipi '
  306. line: "api.ipi IN A 192.168.50.8"
  307. before: '^master01\.ipi '
  308. - regex: '^api-int\.ipi '
  309. line: "api-int.ipi IN A 192.168.50.8"
  310. after: '^api\.ipi '
  311. - regex: '^\*\.apps\.ipi '
  312. line: "*.apps.ipi IN A 192.168.50.9"
  313. after: '^api-int\.ipi '
  314. register: dnsfw_fix
  315. notify:
  316. - reload dns
  317. - name: Increase the serial number of the forward zone if changed.
  318. block:
  319. - name: Load the zone file.
  320. become: yes
  321. ansible.builtin.slurp:
  322. src: /var/named/ocp4.example.com.db
  323. register: zonefile_fw
  324. - name: Read the serial number from the zone file and increase it by one.
  325. ansible.builtin.set_fact:
  326. new_fw_serial: "{{ (zonefile_fw.content | ansible.builtin.b64decode() | ansible.builtin.regex_search('^.*; serial', ignorecase=True, multiline=True) | ansible.builtin.regex_replace('; serial.*$', '') | trim | int) + 1 }}"
  327. - name: Insert the new serial number instead of the old one.
  328. become: yes
  329. ansible.builtin.lineinfile:
  330. path: /var/named/ocp4.example.com.db
  331. regexp: "; serial"
  332. line: " {{ new_fw_serial }} ; serial"
  333. when: dnsfw_fix.changed
  334. - name: Ensure reverse DNS records are there.
  335. become: yes
  336. ansible.builtin.lineinfile:
  337. path: /var/named/ocp4.example.com.reverse.db
  338. regexp: '^{{ item.addr }}\s+IN\s+PTR'
  339. line: "{{ item.addr }} IN PTR {{ item.host }}"
  340. insertbefore: "^40 IN PTR idm"
  341. loop:
  342. - addr: 8
  343. host: apps.ipi.ocp4.example.com.
  344. - addr: 9
  345. host: api.ipi.ocp4.example.com.
  346. - addr: 10
  347. host: master01.ipi.ocp4.example.com.
  348. - addr: 11
  349. host: master02.ipi.ocp4.example.com.
  350. - addr: 12
  351. host: master03.ipi.ocp4.example.com.
  352. - addr: 13
  353. host: worker01.ipi.ocp4.example.com.
  354. - addr: 14
  355. host: worker02.ipi.ocp4.example.com.
  356. register: dnsre_fix
  357. notify:
  358. - reload dns
  359. - name: Increase the serial number of the reverse zone if changed.
  360. block:
  361. - name: Load the zone file.
  362. become: yes
  363. ansible.builtin.slurp:
  364. src: /var/named/ocp4.example.com.reverse.db
  365. register: zonefile_re
  366. - name: Read the serial number from the zone file and increase it by one.
  367. ansible.builtin.set_fact:
  368. new_re_serial: "{{ (zonefile_re.content | ansible.builtin.b64decode() | ansible.builtin.regex_search('^.*; serial', ignorecase=True, multiline=True) | ansible.builtin.regex_replace('; serial.*$', '') | trim | int) + 1 }}"
  369. - name: Insert the new serial number instead of the old one.
  370. become: yes
  371. ansible.builtin.lineinfile:
  372. path: /var/named/ocp4.example.com.reverse.db
  373. regexp: "; serial"
  374. line: " {{ new_re_serial }} ; serial"
  375. when: dnsre_fix.changed
  376. handlers:
  377. - name: restart dhcpd
  378. become: yes
  379. ansible.builtin.systemd_service:
  380. name: dhcpd
  381. state: restarted
  382. - name: reload dns
  383. become: yes
  384. ansible.builtin.systemd_service:
  385. name: named
  386. state: reloaded
  387. ## TODO: wipe the filesystems of all related machines (or warn that a reset is needed if unreachable)
  388. ...