diff options
Diffstat (limited to 'roles/etcd/tasks')
60 files changed, 1410 insertions, 0 deletions
diff --git a/roles/etcd/tasks/auxiliary/clean_data.yml b/roles/etcd/tasks/auxiliary/clean_data.yml new file mode 100644 index 000000000..1ed2db5bc --- /dev/null +++ b/roles/etcd/tasks/auxiliary/clean_data.yml @@ -0,0 +1,5 @@ +--- +- name: Remove member data + file: + path: "{{ etcd_data_dir }}/member" + state: absent diff --git a/roles/etcd/tasks/auxiliary/disable_etcd.yml b/roles/etcd/tasks/auxiliary/disable_etcd.yml new file mode 100644 index 000000000..7c6d0409d --- /dev/null +++ b/roles/etcd/tasks/auxiliary/disable_etcd.yml @@ -0,0 +1,5 @@ +--- +- name: Disable etcd members + service: + name: "{{ etcd_service }}" + state: stopped diff --git a/roles/etcd/tasks/auxiliary/drop_etcdctl.yml b/roles/etcd/tasks/auxiliary/drop_etcdctl.yml new file mode 100644 index 000000000..11bd2310e --- /dev/null +++ b/roles/etcd/tasks/auxiliary/drop_etcdctl.yml @@ -0,0 +1,12 @@ +--- +- name: Install etcd for etcdctl + package: name=etcd{{ '-' + etcd_version if etcd_version is defined else '' }} state=present + when: not openshift.common.is_atomic | bool + +- name: Configure etcd profile.d aliases + template: + dest: "/etc/profile.d/etcdctl.sh" + src: etcdctl.sh.j2 + mode: 0755 + owner: root + group: root diff --git a/roles/etcd/tasks/auxiliary/force_new_cluster.yml b/roles/etcd/tasks/auxiliary/force_new_cluster.yml new file mode 100644 index 000000000..ae8a36130 --- /dev/null +++ b/roles/etcd/tasks/auxiliary/force_new_cluster.yml @@ -0,0 +1,31 @@ +--- +- name: Set ETCD_FORCE_NEW_CLUSTER=true on first etcd host + lineinfile: + line: "ETCD_FORCE_NEW_CLUSTER=true" + dest: /etc/etcd/etcd.conf + backup: true + +- name: Start etcd + systemd: + name: "{{ etcd_service }}" + state: started + +- name: Wait for cluster to become healthy after bringing up first member + command: > + etcdctl --cert-file {{ etcd_peer_cert_file }} --key-file {{ etcd_peer_key_file }} --ca-file {{ etcd_peer_ca_file }} --endpoint https://{{ etcd_peer }}:{{ etcd_client_port }} cluster-health + register: l_etcd_migrate_health + until: l_etcd_migrate_health.rc == 0 + retries: 3 + delay: 30 + +- name: Unset ETCD_FORCE_NEW_CLUSTER=true on first etcd host + lineinfile: + line: "ETCD_FORCE_NEW_CLUSTER=true" + dest: /etc/etcd/etcd.conf + state: absent + backup: true + +- name: Restart first etcd host + systemd: + name: "{{ etcd_service }}" + state: restarted diff --git a/roles/etcd/tasks/backup.archive.yml b/roles/etcd/tasks/backup.archive.yml new file mode 100644 index 000000000..6daa6dc51 --- /dev/null +++ b/roles/etcd/tasks/backup.archive.yml @@ -0,0 +1,3 @@ +--- +- include: backup/vars.yml +- include: backup/archive.yml diff --git a/roles/etcd/tasks/backup.copy.yml b/roles/etcd/tasks/backup.copy.yml new file mode 100644 index 000000000..cc540cbca --- /dev/null +++ b/roles/etcd/tasks/backup.copy.yml @@ -0,0 +1,3 @@ +--- +- include: backup/vars.yml +- include: backup/copy.yml diff --git a/roles/etcd/tasks/backup.fetch.yml b/roles/etcd/tasks/backup.fetch.yml new file mode 100644 index 000000000..26ec15043 --- /dev/null +++ b/roles/etcd/tasks/backup.fetch.yml @@ -0,0 +1,3 @@ +--- +- include: backup/vars.yml +- include: backup/fetch.yml diff --git a/roles/etcd/tasks/backup.force_new_cluster.yml b/roles/etcd/tasks/backup.force_new_cluster.yml new file mode 100644 index 000000000..24bd0540d --- /dev/null +++ b/roles/etcd/tasks/backup.force_new_cluster.yml @@ -0,0 +1,12 @@ +--- +- include: backup/vars.yml + +- name: Move content of etcd backup under the etcd data directory + command: > + mv "{{ l_etcd_backup_dir }}/member" "{{ l_etcd_data_dir }}" + +- name: Set etcd group for the etcd data directory + command: > + chown -R etcd:etcd "{{ l_etcd_data_dir }}" + +- include: auxiliary/force_new_cluster.yml diff --git a/roles/etcd/tasks/backup.unarchive.yml b/roles/etcd/tasks/backup.unarchive.yml new file mode 100644 index 000000000..77a637360 --- /dev/null +++ b/roles/etcd/tasks/backup.unarchive.yml @@ -0,0 +1,3 @@ +--- +- include: backup/vars.yml +- include: backup/unarchive.yml diff --git a/roles/etcd/tasks/backup.yml b/roles/etcd/tasks/backup.yml new file mode 100644 index 000000000..c0538e596 --- /dev/null +++ b/roles/etcd/tasks/backup.yml @@ -0,0 +1,2 @@ +--- +- include: backup/backup.yml diff --git a/roles/etcd/tasks/backup/archive.yml b/roles/etcd/tasks/backup/archive.yml new file mode 100644 index 000000000..f6aa68a6e --- /dev/null +++ b/roles/etcd/tasks/backup/archive.yml @@ -0,0 +1,5 @@ +--- +- name: Archive backup + archive: + path: "{{ l_etcd_backup_dir }}" + dest: "{{ l_etcd_backup_dir }}.tgz" diff --git a/roles/etcd/tasks/backup/backup.yml b/roles/etcd/tasks/backup/backup.yml new file mode 100644 index 000000000..ec1a1989c --- /dev/null +++ b/roles/etcd/tasks/backup/backup.yml @@ -0,0 +1,85 @@ +--- +- include: vars.yml + +# TODO: replace shell module with command and update later checks +- name: Check available disk space for etcd backup + shell: df --output=avail -k {{ l_etcd_data_dir }} | tail -n 1 + register: l_avail_disk + # AUDIT:changed_when: `false` because we are only inspecting + # state, not manipulating anything + changed_when: false + +# TODO: replace shell module with command and update later checks +- name: Check current etcd disk usage + shell: du --exclude='*openshift-backup*' -k {{ l_etcd_data_dir }} | tail -n 1 | cut -f1 + register: l_etcd_disk_usage + # AUDIT:changed_when: `false` because we are only inspecting + # state, not manipulating anything + changed_when: false + +- name: Abort if insufficient disk space for etcd backup + fail: + msg: > + {{ l_etcd_disk_usage.stdout|int*2 }} Kb disk space required for etcd backup, + {{ l_avail_disk.stdout }} Kb available. + when: l_etcd_disk_usage.stdout|int*2 > l_avail_disk.stdout|int + +# For non containerized and non embedded we should have the correct version of +# etcd installed already. So don't do anything. +# +# For containerized installs we now exec into etcd_container +# +# For embedded non containerized we need to ensure we have the latest version +# etcd on the host. +- name: Detecting Atomic Host Operating System + stat: + path: /run/ostree-booted + register: l_ostree_booted + +- name: Install latest etcd for embedded + package: + name: etcd + state: latest + when: + - r_etcd_common_embedded_etcd | bool + - not l_ostree_booted.stat.exists | bool + +- name: Check selinux label of '{{ l_etcd_data_dir }}' + command: > + stat -c '%C' {{ l_etcd_data_dir }} + register: l_etcd_selinux_labels + +- debug: + msg: "{{ l_etcd_selinux_labels }}" + +- name: Make sure the '{{ l_etcd_data_dir }}' has the proper label + command: > + chcon -t svirt_sandbox_file_t "{{ l_etcd_data_dir }}" + when: + - l_etcd_selinux_labels.rc == 0 + - "'svirt_sandbox_file_t' not in l_etcd_selinux_labels.stdout" + +- name: Generate etcd backup + command: > + {{ r_etcd_common_etcdctl_command }} backup --data-dir={{ l_etcd_incontainer_data_dir }} + --backup-dir={{ l_etcd_incontainer_backup_dir }} + +# According to the docs change you can simply copy snap/db +# https://github.com/openshift/openshift-docs/commit/b38042de02d9780842dce95cfa0ef45d53b58bc6 +- name: Check for v3 data store + stat: + path: "{{ l_etcd_data_dir }}/member/snap/db" + register: l_v3_db + +- name: Copy etcd v3 data store + command: > + cp -a {{ l_etcd_data_dir }}/member/snap/db + {{ l_etcd_backup_dir }}/member/snap/ + when: l_v3_db.stat.exists + +- set_fact: + r_etcd_common_backup_complete: True + +- name: Display location of etcd backup + debug: + msg: "Etcd backup created in {{ l_etcd_backup_dir }}" diff --git a/roles/etcd/tasks/backup/copy.yml b/roles/etcd/tasks/backup/copy.yml new file mode 100644 index 000000000..16604bae8 --- /dev/null +++ b/roles/etcd/tasks/backup/copy.yml @@ -0,0 +1,5 @@ +--- +- name: Copy etcd backup + copy: + src: "{{ etcd_backup_sync_directory }}/{{ l_backup_dir_name }}.tgz" + dest: "{{ l_etcd_data_dir }}" diff --git a/roles/etcd/tasks/backup/fetch.yml b/roles/etcd/tasks/backup/fetch.yml new file mode 100644 index 000000000..610ce1960 --- /dev/null +++ b/roles/etcd/tasks/backup/fetch.yml @@ -0,0 +1,8 @@ +--- +- name: Fetch etcd backup + fetch: + src: "{{ l_etcd_backup_dir }}.tgz" + dest: "{{ etcd_backup_sync_directory }}/" + flat: yes + fail_on_missing: yes + validate_checksum: yes diff --git a/roles/etcd/tasks/backup/unarchive.yml b/roles/etcd/tasks/backup/unarchive.yml new file mode 100644 index 000000000..6c75d00a7 --- /dev/null +++ b/roles/etcd/tasks/backup/unarchive.yml @@ -0,0 +1,14 @@ +--- +- shell: ls /var/lib/etcd + register: output + +- debug: + msg: "output: {{ output }}" + +- name: Unarchive backup + # can't use unarchive https://github.com/ansible/ansible/issues/30821 + # unarchive: + # src: "{{ l_etcd_backup_dir }}.tgz" + # dest: "{{ l_etcd_backup_dir }}" + command: > + tar -xf "{{ l_etcd_backup_dir }}.tgz" -C "{{ l_etcd_data_dir }}" diff --git a/roles/etcd/tasks/backup/vars.yml b/roles/etcd/tasks/backup/vars.yml new file mode 100644 index 000000000..3c009f557 --- /dev/null +++ b/roles/etcd/tasks/backup/vars.yml @@ -0,0 +1,18 @@ +--- +# set the etcd backup directory name here in case the tag or sufix consists of dynamic value that changes over time +# e.g. openshift-backup-{{ lookup('pipe', 'date +%Y%m%d%H%M%S') }} value will change every second so if the date changes +# right after setting l_etcd_incontainer_backup_dir and before l_etcd_backup_dir facts, the backup directory name is different +- set_fact: + l_backup_dir_name: "openshift-backup-{{ r_etcd_common_backup_tag }}{{ r_etcd_common_backup_sufix_name }}" + +- set_fact: + l_etcd_data_dir: "{{ etcd_data_dir }}{{ '/etcd.etcd' if r_etcd_common_etcd_runtime == 'runc' else '' }}" + +- set_fact: + l_etcd_incontainer_data_dir: "{{ etcd_data_dir }}" + +- set_fact: + l_etcd_incontainer_backup_dir: "{{ l_etcd_incontainer_data_dir }}/{{ l_backup_dir_name }}" + +- set_fact: + l_etcd_backup_dir: "{{ l_etcd_data_dir }}/{{ l_backup_dir_name }}" diff --git a/roles/etcd/tasks/backup_ca_certificates.yml b/roles/etcd/tasks/backup_ca_certificates.yml new file mode 100644 index 000000000..a41b032f3 --- /dev/null +++ b/roles/etcd/tasks/backup_ca_certificates.yml @@ -0,0 +1,2 @@ +--- +- include: certificates/backup_ca_certificates.yml diff --git a/roles/etcd/tasks/backup_generated_certificates.yml b/roles/etcd/tasks/backup_generated_certificates.yml new file mode 100644 index 000000000..8cf2a10cc --- /dev/null +++ b/roles/etcd/tasks/backup_generated_certificates.yml @@ -0,0 +1,2 @@ +--- +- include: certificates/backup_generated_certificates.yml diff --git a/roles/etcd/tasks/backup_master_etcd_certificates.yml b/roles/etcd/tasks/backup_master_etcd_certificates.yml new file mode 100644 index 000000000..129e1831c --- /dev/null +++ b/roles/etcd/tasks/backup_master_etcd_certificates.yml @@ -0,0 +1,2 @@ +--- +- include: certificates/backup_master_etcd_certificates.yml diff --git a/roles/etcd/tasks/backup_server_certificates.yml b/roles/etcd/tasks/backup_server_certificates.yml new file mode 100644 index 000000000..267ffeb4d --- /dev/null +++ b/roles/etcd/tasks/backup_server_certificates.yml @@ -0,0 +1,2 @@ +--- +- include: certificates/backup_server_certificates.yml diff --git a/roles/etcd/tasks/ca.yml b/roles/etcd/tasks/ca.yml new file mode 100644 index 000000000..cca1e9ad7 --- /dev/null +++ b/roles/etcd/tasks/ca.yml @@ -0,0 +1,2 @@ +--- +- include: certificates/deploy_ca.yml diff --git a/roles/etcd/tasks/certificates/backup_ca_certificates.yml b/roles/etcd/tasks/certificates/backup_ca_certificates.yml new file mode 100644 index 000000000..f60eb82ef --- /dev/null +++ b/roles/etcd/tasks/certificates/backup_ca_certificates.yml @@ -0,0 +1,12 @@ +--- +- name: Determine if CA certificate directory exists + stat: + path: "{{ etcd_ca_dir }}" + register: etcd_ca_certs_dir_stat +- name: Backup generated etcd certificates + command: > + tar -czf {{ etcd_conf_dir }}/etcd-ca-certificate-backup-{{ ansible_date_time.epoch }}.tgz + {{ etcd_ca_dir }} + args: + warn: no + when: etcd_ca_certs_dir_stat.stat.exists | bool diff --git a/roles/etcd/tasks/certificates/backup_generated_certificates.yml b/roles/etcd/tasks/certificates/backup_generated_certificates.yml new file mode 100644 index 000000000..6a24cfcb3 --- /dev/null +++ b/roles/etcd/tasks/certificates/backup_generated_certificates.yml @@ -0,0 +1,13 @@ +--- +- name: Determine if generated etcd certificates exist + stat: + path: "{{ etcd_conf_dir }}/generated_certs" + register: etcd_generated_certs_dir_stat + +- name: Backup generated etcd certificates + command: > + tar -czf {{ etcd_conf_dir }}/etcd-generated-certificate-backup-{{ ansible_date_time.epoch }}.tgz + {{ etcd_conf_dir }}/generated_certs + args: + warn: no + when: etcd_generated_certs_dir_stat.stat.exists | bool diff --git a/roles/etcd/tasks/certificates/backup_master_etcd_certificates.yml b/roles/etcd/tasks/certificates/backup_master_etcd_certificates.yml new file mode 100644 index 000000000..e65b3e5a2 --- /dev/null +++ b/roles/etcd/tasks/certificates/backup_master_etcd_certificates.yml @@ -0,0 +1,7 @@ +--- +- name: Backup master etcd certificates + shell: > + tar -czvf /etc/origin/master/master-etcd-certificate-backup-{{ ansible_date_time.epoch }}.tgz + /etc/origin/master/master.etcd-* + args: + warn: no diff --git a/roles/etcd/tasks/certificates/backup_server_certificates.yml b/roles/etcd/tasks/certificates/backup_server_certificates.yml new file mode 100644 index 000000000..8e6cc6965 --- /dev/null +++ b/roles/etcd/tasks/certificates/backup_server_certificates.yml @@ -0,0 +1,11 @@ +--- +- name: Backup etcd certificates + command: > + tar -czvf /etc/etcd/etcd-server-certificate-backup-{{ ansible_date_time.epoch }}.tgz + {{ etcd_conf_dir }}/ca.crt + {{ etcd_conf_dir }}/server.crt + {{ etcd_conf_dir }}/server.key + {{ etcd_conf_dir }}/peer.crt + {{ etcd_conf_dir }}/peer.key + args: + warn: no diff --git a/roles/etcd/tasks/certificates/deploy_ca.yml b/roles/etcd/tasks/certificates/deploy_ca.yml new file mode 100644 index 000000000..3d32290a2 --- /dev/null +++ b/roles/etcd/tasks/certificates/deploy_ca.yml @@ -0,0 +1,78 @@ +--- +- name: Install openssl + package: + name: openssl + state: present + when: not etcd_is_atomic | bool + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- file: + path: "{{ item }}" + state: directory + mode: 0700 + owner: root + group: root + with_items: + - "{{ etcd_ca_new_certs_dir }}" + - "{{ etcd_ca_crl_dir }}" + - "{{ etcd_ca_dir }}/fragments" + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- command: cp /etc/pki/tls/openssl.cnf ./ + args: + chdir: "{{ etcd_ca_dir }}/fragments" + creates: "{{ etcd_ca_dir }}/fragments/openssl.cnf" + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- template: + dest: "{{ etcd_ca_dir }}/fragments/openssl_append.cnf" + src: openssl_append.j2 + backup: true + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- assemble: + src: "{{ etcd_ca_dir }}/fragments" + dest: "{{ etcd_openssl_conf }}" + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- name: Check etcd_ca_db exist + stat: path="{{ etcd_ca_db }}" + register: etcd_ca_db_check + changed_when: false + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- name: Touch etcd_ca_db file + file: + path: "{{ etcd_ca_db }}" + state: touch + when: etcd_ca_db_check.stat.isreg is not defined + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- copy: + dest: "{{ etcd_ca_serial }}" + content: "01" + force: no + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- name: Create etcd CA certificate + command: > + openssl req -config {{ etcd_openssl_conf }} -newkey rsa:4096 + -keyout {{ etcd_ca_key }} -new -out {{ etcd_ca_cert }} + -x509 -extensions {{ etcd_ca_exts_self }} -batch -nodes + -days {{ etcd_ca_default_days }} + -subj /CN=etcd-signer@{{ ansible_date_time.epoch }} + args: + chdir: "{{ etcd_ca_dir }}" + creates: "{{ etcd_ca_cert }}" + environment: + SAN: 'etcd-signer' + delegate_to: "{{ etcd_ca_host }}" + run_once: true diff --git a/roles/etcd/tasks/certificates/distribute_ca.yml b/roles/etcd/tasks/certificates/distribute_ca.yml new file mode 100644 index 000000000..632ac15dd --- /dev/null +++ b/roles/etcd/tasks/certificates/distribute_ca.yml @@ -0,0 +1,47 @@ +--- +- name: Create a tarball of the etcd ca certs + command: > + tar -czvf {{ etcd_conf_dir }}/{{ etcd_ca_name }}.tgz + -C {{ etcd_ca_dir }} . + args: + creates: "{{ etcd_conf_dir }}/{{ etcd_ca_name }}.tgz" + warn: no + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- name: Retrieve etcd ca cert tarball + fetch: + src: "{{ etcd_conf_dir }}/{{ etcd_ca_name }}.tgz" + dest: "{{ etcd_sync_cert_dir }}/" + flat: yes + fail_on_missing: yes + validate_checksum: yes + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- name: Ensure ca directory exists + file: + path: "{{ etcd_ca_dir }}" + state: directory + +- name: Unarchive etcd ca cert tarballs + unarchive: + src: "{{ etcd_sync_cert_dir }}/{{ etcd_ca_name }}.tgz" + dest: "{{ etcd_ca_dir }}" + +- name: Read current etcd CA + slurp: + src: "{{ etcd_conf_dir }}/ca.crt" + register: g_current_etcd_ca_output + +- name: Read new etcd CA + slurp: + src: "{{ etcd_ca_dir }}/ca.crt" + register: g_new_etcd_ca_output + +- copy: + content: "{{ (g_new_etcd_ca_output.content|b64decode) + (g_current_etcd_ca_output.content|b64decode) }}" + dest: "{{ item }}/ca.crt" + with_items: + - "{{ etcd_conf_dir }}" + - "{{ etcd_ca_dir }}" diff --git a/roles/etcd/tasks/certificates/fetch_client_certificates_from_ca.yml b/roles/etcd/tasks/certificates/fetch_client_certificates_from_ca.yml new file mode 100644 index 000000000..119071a72 --- /dev/null +++ b/roles/etcd/tasks/certificates/fetch_client_certificates_from_ca.yml @@ -0,0 +1,138 @@ +--- +- name: Ensure CA certificate exists on etcd_ca_host + stat: + path: "{{ etcd_ca_cert }}" + register: g_ca_cert_stat_result + delegate_to: "{{ etcd_ca_host }}" + run_once: true + +- fail: + msg: > + CA certificate {{ etcd_ca_cert }} doesn't exist on CA host + {{ etcd_ca_host }}. Apply 'etcd_ca' action from `etcd` role to + {{ etcd_ca_host }}. + when: not g_ca_cert_stat_result.stat.exists | bool + run_once: true + +- name: Check status of external etcd certificatees + stat: + path: "{{ etcd_cert_config_dir }}/{{ item }}" + with_items: + - "{{ etcd_cert_prefix }}client.crt" + - "{{ etcd_cert_prefix }}client.key" + - "{{ etcd_cert_prefix }}ca.crt" + register: g_external_etcd_cert_stat_result + when: not etcd_certificates_redeploy | default(false) | bool + +- set_fact: + etcd_client_certs_missing: "{{ true if etcd_certificates_redeploy | default(false) | bool + else (False in (g_external_etcd_cert_stat_result.results + | default({}) + | oo_collect(attribute='stat.exists') + | list)) }}" + +- name: Ensure generated_certs directory present + file: + path: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + state: directory + mode: 0700 + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Create the client csr + command: > + openssl req -new -keyout {{ etcd_cert_prefix }}client.key + -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}client.csr + -reqexts {{ etcd_req_ext }} -batch -nodes + -subj /CN={{ etcd_hostname }} + args: + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'client.csr' }}" + environment: + SAN: "IP:{{ etcd_ip }},DNS:{{ etcd_hostname }}" + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +# Certificates must be signed serially in order to avoid competing +# for the serial file. +- name: Sign and create the client crt + delegated_serial_command: + command: > + openssl ca -name {{ etcd_ca_name }} -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}client.crt + -in {{ etcd_cert_prefix }}client.csr + -batch + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'client.crt' }}" + environment: + SAN: "IP:{{ etcd_ip }}" + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- file: + src: "{{ etcd_ca_cert }}" + dest: "{{ etcd_generated_certs_dir}}/{{ etcd_cert_subdir }}/{{ etcd_cert_prefix }}ca.crt" + state: hard + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Create local temp directory for syncing certs + local_action: command mktemp -d /tmp/etcd_certificates-XXXXXXX + register: g_etcd_client_mktemp + changed_when: False + when: etcd_client_certs_missing | bool + become: no + +- name: Create a tarball of the etcd certs + command: > + tar -czvf {{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz + -C {{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }} . + args: + creates: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz" + # Disables the following warning: + # Consider using unarchive module rather than running tar + warn: no + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Retrieve the etcd cert tarballs + fetch: + src: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz" + dest: "{{ g_etcd_client_mktemp.stdout }}/" + flat: yes + fail_on_missing: yes + validate_checksum: yes + when: etcd_client_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Ensure certificate directory exists + file: + path: "{{ etcd_cert_config_dir }}" + state: directory + when: etcd_client_certs_missing | bool + +- name: Unarchive etcd cert tarballs + unarchive: + src: "{{ g_etcd_client_mktemp.stdout }}/{{ etcd_cert_subdir }}.tgz" + dest: "{{ etcd_cert_config_dir }}" + when: etcd_client_certs_missing | bool + +- file: + path: "{{ etcd_cert_config_dir }}/{{ item }}" + owner: root + group: root + mode: 0600 + with_items: + - "{{ etcd_cert_prefix }}client.crt" + - "{{ etcd_cert_prefix }}client.key" + - "{{ etcd_cert_prefix }}ca.crt" + when: etcd_client_certs_missing | bool + +- name: Delete temporary directory + local_action: file path="{{ g_etcd_client_mktemp.stdout }}" state=absent + changed_when: False + when: etcd_client_certs_missing | bool + become: no diff --git a/roles/etcd/tasks/certificates/fetch_server_certificates_from_ca.yml b/roles/etcd/tasks/certificates/fetch_server_certificates_from_ca.yml new file mode 100644 index 000000000..26492fb3c --- /dev/null +++ b/roles/etcd/tasks/certificates/fetch_server_certificates_from_ca.yml @@ -0,0 +1,234 @@ +--- +- name: Install etcd + package: + name: "etcd{{ '-' + etcd_version if etcd_version is defined else '' }}" + state: present + when: not etcd_is_containerized | bool + +- name: Check status of etcd certificates + stat: + path: "{{ item }}" + with_items: + - "{{ etcd_cert_config_dir }}/{{ etcd_cert_prefix }}server.crt" + - "{{ etcd_cert_config_dir }}/{{ etcd_cert_prefix }}peer.crt" + - "{{ etcd_cert_config_dir }}/{{ etcd_cert_prefix }}ca.crt" + - "{{ etcd_system_container_cert_config_dir }}/{{ etcd_cert_prefix }}server.crt" + - "{{ etcd_system_container_cert_config_dir }}/{{ etcd_cert_prefix }}peer.crt" + - "{{ etcd_system_container_cert_config_dir }}/{{ etcd_cert_prefix }}ca.crt" + register: g_etcd_server_cert_stat_result + when: not etcd_certificates_redeploy | default(false) | bool + +- set_fact: + etcd_server_certs_missing: "{{ true if etcd_certificates_redeploy | default(false) | bool + else (False in (g_etcd_server_cert_stat_result.results + | default({}) + | oo_collect(attribute='stat.exists') + | list)) }}" + +- name: Ensure generated_certs directory present + file: + path: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + state: directory + mode: 0700 + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Create the server csr + command: > + openssl req -new -keyout {{ etcd_cert_prefix }}server.key + -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}server.csr + -reqexts {{ etcd_req_ext }} -batch -nodes + -subj /CN={{ etcd_hostname }} + args: + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'server.csr' }}" + environment: + SAN: "IP:{{ etcd_ip }},DNS:{{ etcd_hostname }}" + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +# Certificates must be signed serially in order to avoid competing +# for the serial file. +- name: Sign and create the server crt + delegated_serial_command: + command: > + openssl ca -name {{ etcd_ca_name }} -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}server.crt + -in {{ etcd_cert_prefix }}server.csr + -extensions {{ etcd_ca_exts_server }} -batch + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'server.crt' }}" + environment: + SAN: "IP:{{ etcd_ip }}" + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Create the peer csr + command: > + openssl req -new -keyout {{ etcd_cert_prefix }}peer.key + -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}peer.csr + -reqexts {{ etcd_req_ext }} -batch -nodes + -subj /CN={{ etcd_hostname }} + args: + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'peer.csr' }}" + environment: + SAN: "IP:{{ etcd_ip }},DNS:{{ etcd_hostname }}" + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +# Certificates must be signed serially in order to avoid competing +# for the serial file. +- name: Sign and create the peer crt + delegated_serial_command: + command: > + openssl ca -name {{ etcd_ca_name }} -config {{ etcd_openssl_conf }} + -out {{ etcd_cert_prefix }}peer.crt + -in {{ etcd_cert_prefix }}peer.csr + -extensions {{ etcd_ca_exts_peer }} -batch + chdir: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}" + creates: "{{ etcd_generated_certs_dir ~ '/' ~ etcd_cert_subdir ~ '/' + ~ etcd_cert_prefix ~ 'peer.crt' }}" + environment: + SAN: "IP:{{ etcd_ip }}" + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- file: + src: "{{ etcd_ca_cert }}" + dest: "{{ etcd_generated_certs_dir}}/{{ etcd_cert_subdir }}/{{ etcd_cert_prefix }}ca.crt" + state: hard + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Create local temp directory for syncing certs + local_action: command mktemp -d /tmp/etcd_certificates-XXXXXXX + become: no + register: g_etcd_server_mktemp + changed_when: False + when: etcd_server_certs_missing | bool + +- name: Create a tarball of the etcd certs + command: > + tar -czvf {{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz + -C {{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }} . + args: + creates: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz" + # Disables the following warning: + # Consider using unarchive module rather than running tar + warn: no + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Retrieve etcd cert tarball + fetch: + src: "{{ etcd_generated_certs_dir }}/{{ etcd_cert_subdir }}.tgz" + dest: "{{ g_etcd_server_mktemp.stdout }}/" + flat: yes + fail_on_missing: yes + validate_checksum: yes + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Ensure certificate directory exists + file: + path: "{{ item }}" + state: directory + with_items: + - "{{ etcd_cert_config_dir }}" + - "{{ etcd_system_container_cert_config_dir }}" + when: etcd_server_certs_missing | bool + +- name: Unarchive cert tarball + unarchive: + src: "{{ g_etcd_server_mktemp.stdout }}/{{ etcd_cert_subdir }}.tgz" + dest: "{{ etcd_cert_config_dir }}" + when: etcd_server_certs_missing | bool + +- name: Create a tarball of the etcd ca certs + command: > + tar -czvf {{ etcd_generated_certs_dir }}/{{ etcd_ca_name }}.tgz + -C {{ etcd_ca_dir }} . + args: + creates: "{{ etcd_generated_certs_dir }}/{{ etcd_ca_name }}.tgz" + warn: no + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Retrieve etcd ca cert tarball + fetch: + src: "{{ etcd_generated_certs_dir }}/{{ etcd_ca_name }}.tgz" + dest: "{{ g_etcd_server_mktemp.stdout }}/" + flat: yes + fail_on_missing: yes + validate_checksum: yes + when: etcd_server_certs_missing | bool + delegate_to: "{{ etcd_ca_host }}" + +- name: Ensure ca directory exists + file: + path: "{{ item }}" + state: directory + with_items: + - "{{ etcd_ca_dir }}" + - "{{ etcd_system_container_cert_config_dir }}/ca" + when: etcd_server_certs_missing | bool + +- name: Unarchive cert tarball for the system container + unarchive: + src: "{{ g_etcd_server_mktemp.stdout }}/{{ etcd_cert_subdir }}.tgz" + dest: "{{ etcd_system_container_cert_config_dir }}" + when: + - etcd_server_certs_missing | bool + - r_etcd_common_etcd_runtime == 'runc' + +- name: Unarchive etcd ca cert tarballs for the system container + unarchive: + src: "{{ g_etcd_server_mktemp.stdout }}/{{ etcd_ca_name }}.tgz" + dest: "{{ etcd_system_container_cert_config_dir }}/ca" + when: + - etcd_server_certs_missing | bool + - r_etcd_common_etcd_runtime == 'runc' + +- name: Delete temporary directory + local_action: file path="{{ g_etcd_server_mktemp.stdout }}" state=absent + become: no + changed_when: False + when: etcd_server_certs_missing | bool + +- name: Validate permissions on certificate files + file: + path: "{{ item }}" + mode: 0600 + owner: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + group: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + when: etcd_url_scheme == 'https' + with_items: + - "{{ etcd_ca_file }}" + - "{{ etcd_cert_file }}" + - "{{ etcd_key_file }}" + +- name: Validate permissions on peer certificate files + file: + path: "{{ item }}" + mode: 0600 + owner: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + group: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + when: etcd_peer_url_scheme == 'https' + with_items: + - "{{ etcd_peer_ca_file }}" + - "{{ etcd_peer_cert_file }}" + - "{{ etcd_peer_key_file }}" + +- name: Validate permissions on the config dir + file: + path: "{{ etcd_conf_dir }}" + state: directory + owner: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + group: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + mode: 0700 diff --git a/roles/etcd/tasks/certificates/remove_ca_certificates.yml b/roles/etcd/tasks/certificates/remove_ca_certificates.yml new file mode 100644 index 000000000..4a86eb60d --- /dev/null +++ b/roles/etcd/tasks/certificates/remove_ca_certificates.yml @@ -0,0 +1,5 @@ +--- +- name: Remove CA certificate directory + file: + path: "{{ etcd_ca_dir }}" + state: absent diff --git a/roles/etcd/tasks/certificates/remove_generated_certificates.yml b/roles/etcd/tasks/certificates/remove_generated_certificates.yml new file mode 100644 index 000000000..993b18de2 --- /dev/null +++ b/roles/etcd/tasks/certificates/remove_generated_certificates.yml @@ -0,0 +1,5 @@ +--- +- name: Remove generated etcd certificates + file: + path: "{{ etcd_conf_dir }}/generated_certs" + state: absent diff --git a/roles/etcd/tasks/certificates/retrieve_ca_certificates.yml b/roles/etcd/tasks/certificates/retrieve_ca_certificates.yml new file mode 100644 index 000000000..70b5c6523 --- /dev/null +++ b/roles/etcd/tasks/certificates/retrieve_ca_certificates.yml @@ -0,0 +1,8 @@ +--- +- name: Retrieve etcd CA certificate + fetch: + src: "{{ etcd_conf_dir }}/ca.crt" + dest: "{{ etcd_sync_cert_dir }}/" + flat: yes + fail_on_missing: yes + validate_checksum: yes diff --git a/roles/etcd/tasks/check_cluster_health.yml b/roles/etcd/tasks/check_cluster_health.yml new file mode 100644 index 000000000..75c110972 --- /dev/null +++ b/roles/etcd/tasks/check_cluster_health.yml @@ -0,0 +1,2 @@ +--- +- include: migration/check_cluster_health.yml diff --git a/roles/etcd/tasks/clean_data.yml b/roles/etcd/tasks/clean_data.yml new file mode 100644 index 000000000..d131ffd21 --- /dev/null +++ b/roles/etcd/tasks/clean_data.yml @@ -0,0 +1,2 @@ +--- +- include: auxiliary/clean_data.yml diff --git a/roles/etcd/tasks/client_certificates.yml b/roles/etcd/tasks/client_certificates.yml new file mode 100644 index 000000000..2f4108a0d --- /dev/null +++ b/roles/etcd/tasks/client_certificates.yml @@ -0,0 +1,2 @@ +--- +- include: certificates/fetch_client_certificates_from_ca.yml diff --git a/roles/etcd/tasks/disable_etcd.yml b/roles/etcd/tasks/disable_etcd.yml new file mode 100644 index 000000000..9202e6e48 --- /dev/null +++ b/roles/etcd/tasks/disable_etcd.yml @@ -0,0 +1,2 @@ +--- +- include: auxiliary/disable_etcd.yml diff --git a/roles/etcd/tasks/distribute_ca b/roles/etcd/tasks/distribute_ca new file mode 100644 index 000000000..040c5f7af --- /dev/null +++ b/roles/etcd/tasks/distribute_ca @@ -0,0 +1,2 @@ +--- +- include: certificates/distribute_ca.yml diff --git a/roles/etcd/tasks/drop_etcdctl.yml b/roles/etcd/tasks/drop_etcdctl.yml new file mode 100644 index 000000000..4c1f609f7 --- /dev/null +++ b/roles/etcd/tasks/drop_etcdctl.yml @@ -0,0 +1,2 @@ +--- +- include: auxiliary/drop_etcdctl.yml diff --git a/roles/etcd/tasks/fetch_backup.yml b/roles/etcd/tasks/fetch_backup.yml new file mode 100644 index 000000000..513eed17a --- /dev/null +++ b/roles/etcd/tasks/fetch_backup.yml @@ -0,0 +1,8 @@ +--- +- include: backup/vars.yml + +- include: backup/archive.yml + +- include: backup/sync_backup.yml + +- include: backup/ diff --git a/roles/etcd/tasks/firewall.yml b/roles/etcd/tasks/firewall.yml new file mode 100644 index 000000000..4d0f6290a --- /dev/null +++ b/roles/etcd/tasks/firewall.yml @@ -0,0 +1,40 @@ +--- +- when: r_etcd_firewall_enabled | bool and not r_etcd_use_firewalld | bool + block: + - name: Add iptables allow rules + os_firewall_manage_iptables: + name: "{{ item.service }}" + action: add + protocol: "{{ item.port.split('/')[1] }}" + port: "{{ item.port.split('/')[0] }}" + when: item.cond | default(True) + with_items: "{{ r_etcd_os_firewall_allow }}" + + - name: Remove iptables rules + os_firewall_manage_iptables: + name: "{{ item.service }}" + action: remove + protocol: "{{ item.port.split('/')[1] }}" + port: "{{ item.port.split('/')[0] }}" + when: item.cond | default(True) + with_items: "{{ r_etcd_os_firewall_deny }}" + +- when: r_etcd_firewall_enabled | bool and r_etcd_use_firewalld | bool + block: + - name: Add firewalld allow rules + firewalld: + port: "{{ item.port }}" + permanent: true + immediate: true + state: enabled + when: item.cond | default(True) + with_items: "{{ r_etcd_os_firewall_allow }}" + + - name: Remove firewalld allow rules + firewalld: + port: "{{ item.port }}" + permanent: true + immediate: true + state: disabled + when: item.cond | default(True) + with_items: "{{ r_etcd_os_firewall_deny }}" diff --git a/roles/etcd/tasks/main.yml b/roles/etcd/tasks/main.yml new file mode 100644 index 000000000..3e69af314 --- /dev/null +++ b/roles/etcd/tasks/main.yml @@ -0,0 +1,133 @@ +--- +- name: Set hostname and ip facts + set_fact: + # Store etcd_hostname and etcd_ip such that they will be available + # in hostvars. Defaults for these variables are set in etcd_common. + etcd_hostname: "{{ etcd_hostname }}" + etcd_ip: "{{ etcd_ip }}" + +- name: setup firewall + include: firewall.yml + static: yes + +- name: Install etcd + package: name=etcd{{ '-' + etcd_version if etcd_version is defined else '' }} state=present + when: not etcd_is_containerized | bool + +- include: drop_etcdctl.yml + when: + - openshift_etcd_etcdctl_profile | default(true) | bool + +- block: + - name: Pull etcd container + command: docker pull {{ openshift.etcd.etcd_image }} + register: pull_result + changed_when: "'Downloaded newer image' in pull_result.stdout" + + - name: Install etcd container service file + template: + dest: "/etc/systemd/system/etcd_container.service" + src: etcd.docker.service + when: + - etcd_is_containerized | bool + - not openshift.common.is_etcd_system_container | bool + +# Start secondary etcd instance for third party integrations +# TODO: Determine an alternative to using thirdparty variable +- block: + - name: Create configuration directory + file: + path: "{{ etcd_conf_dir }}" + state: directory + mode: 0700 + + # TODO: retest with symlink to confirm it does or does not function + - name: Copy service file for etcd instance + copy: + src: /usr/lib/systemd/system/etcd.service + dest: "/etc/systemd/system/{{ etcd_service }}.service" + remote_src: True + + - name: Create third party etcd service.d directory exists + file: + path: "{{ etcd_systemd_dir }}" + state: directory + + - name: Configure third part etcd service unit file + template: + dest: "{{ etcd_systemd_dir }}/custom.conf" + src: custom.conf.j2 + when: etcd_is_thirdparty + + # TODO: this task may not be needed with Validate permissions +- name: Ensure etcd datadir exists + file: + path: "{{ etcd_data_dir }}" + state: directory + mode: 0700 + when: etcd_is_containerized | bool + +- name: Ensure etcd datadir ownership for thirdparty datadir + file: + path: "{{ etcd_data_dir }}" + state: directory + mode: 0700 + owner: etcd + group: etcd + recurse: True + when: etcd_is_thirdparty | bool + + # TODO: Determine if the below reload would work here, for now just reload +- name: + command: systemctl daemon-reload + when: etcd_is_thirdparty | bool + +- block: + - name: Disable system etcd when containerized + systemd: + name: etcd + state: stopped + enabled: no + masked: yes + daemon_reload: yes + when: not openshift.common.is_etcd_system_container | bool + register: task_result + failed_when: task_result|failed and 'could not' not in task_result.msg|lower + + - name: Install etcd container service file + template: + dest: "/etc/systemd/system/etcd_container.service" + src: etcd.docker.service + when: not openshift.common.is_etcd_system_container | bool + + - name: Install Etcd system container + include: system_container.yml + when: openshift.common.is_etcd_system_container | bool + when: etcd_is_containerized | bool + +- name: Validate permissions on the config dir + file: + path: "{{ etcd_conf_dir }}" + state: directory + owner: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + group: "{{ 'etcd' if not etcd_is_containerized | bool else omit }}" + mode: 0700 + +- name: Write etcd global config file + template: + src: etcd.conf.j2 + dest: "{{ etcd_conf_file }}" + backup: true + notify: + - restart etcd + +- name: Enable etcd + systemd: + name: "{{ etcd_service }}" + state: started + enabled: yes + register: start_result + +- name: Set fact etcd_service_status_changed + set_fact: + etcd_service_status_changed: "{{ start_result | changed }}" diff --git a/roles/etcd/tasks/migrate.add_ttls.yml b/roles/etcd/tasks/migrate.add_ttls.yml new file mode 100644 index 000000000..bc27e4ea1 --- /dev/null +++ b/roles/etcd/tasks/migrate.add_ttls.yml @@ -0,0 +1,2 @@ +--- +- include: migration/add_ttls.yml diff --git a/roles/etcd/tasks/migrate.configure_master.yml b/roles/etcd/tasks/migrate.configure_master.yml new file mode 100644 index 000000000..3ada6e362 --- /dev/null +++ b/roles/etcd/tasks/migrate.configure_master.yml @@ -0,0 +1,2 @@ +--- +- include: migration/configure_master.yml diff --git a/roles/etcd/tasks/migrate.pre_check.yml b/roles/etcd/tasks/migrate.pre_check.yml new file mode 100644 index 000000000..124d21561 --- /dev/null +++ b/roles/etcd/tasks/migrate.pre_check.yml @@ -0,0 +1,2 @@ +--- +- include: migration/check.yml diff --git a/roles/etcd/tasks/migrate.yml b/roles/etcd/tasks/migrate.yml new file mode 100644 index 000000000..5d5385873 --- /dev/null +++ b/roles/etcd/tasks/migrate.yml @@ -0,0 +1,2 @@ +--- +- include: migration/migrate.yml diff --git a/roles/etcd/tasks/migration/add_ttls.yml b/roles/etcd/tasks/migration/add_ttls.yml new file mode 100644 index 000000000..14625e49e --- /dev/null +++ b/roles/etcd/tasks/migration/add_ttls.yml @@ -0,0 +1,34 @@ +--- +# To be executed on first master +- slurp: + src: "{{ openshift.common.config_base }}/master/master-config.yaml" + register: g_master_config_output + +- set_fact: + accessTokenMaxAgeSeconds: "{{ (g_master_config_output.content|b64decode|from_yaml).oauthConfig.tokenConfig.accessTokenMaxAgeSeconds | default(86400) }}" + authroizeTokenMaxAgeSeconds: "{{ (g_master_config_output.content|b64decode|from_yaml).oauthConfig.tokenConfig.authroizeTokenMaxAgeSeconds | default(500) }}" + controllerLeaseTTL: "{{ (g_master_config_output.content|b64decode|from_yaml).controllerLeaseTTL | default(30) }}" + +- name: Re-introduce leases (as a replacement for key TTLs) + command: > + oadm migrate etcd-ttl \ + --cert {{ r_etcd_common_master_peer_cert_file }} \ + --key {{ r_etcd_common_master_peer_key_file }} \ + --cacert {{ r_etcd_common_master_peer_ca_file }} \ + --etcd-address 'https://{{ etcd_peer }}:{{ etcd_client_port }}' \ + --ttl-keys-prefix {{ item.keys }} \ + --lease-duration {{ item.ttl }} + environment: + ETCDCTL_API: 3 + PATH: "/usr/local/bin:/var/usrlocal/bin:{{ ansible_env.PATH }}" + with_items: + - keys: "/kubernetes.io/events" + ttl: "1h" + - keys: "/kubernetes.io/masterleases" + ttl: "10s" + - keys: "/openshift.io/oauth/accesstokens" + ttl: "{{ accessTokenMaxAgeSeconds }}s" + - keys: "/openshift.io/oauth/authorizetokens" + ttl: "{{ authroizeTokenMaxAgeSeconds }}s" + - keys: "/openshift.io/leases/controllers" + ttl: "{{ controllerLeaseTTL }}s" diff --git a/roles/etcd/tasks/migration/check.yml b/roles/etcd/tasks/migration/check.yml new file mode 100644 index 000000000..0804d9e1c --- /dev/null +++ b/roles/etcd/tasks/migration/check.yml @@ -0,0 +1,56 @@ +--- + +# Check the cluster is healthy +- include: check_cluster_health.yml + +# Check if the member has v3 data already +# Run the migration only if the data are v2 +- name: Check if there are any v3 data + command: > + etcdctl --cert {{ etcd_peer_cert_file }} --key {{ etcd_peer_key_file }} --cacert {{ etcd_peer_ca_file }} --endpoints 'https://{{ etcd_peer }}:{{ etcd_client_port }}' get "" --from-key --keys-only -w json --limit 1 + environment: + ETCDCTL_API: 3 + register: l_etcdctl_output + +- fail: + msg: "Unable to get a number of v3 keys" + when: l_etcdctl_output.rc != 0 + +- fail: + msg: "The etcd has at least one v3 key" + when: "'count' in (l_etcdctl_output.stdout | from_json) and (l_etcdctl_output.stdout | from_json).count != 0" + + +# TODO(jchaloup): once the until loop can be used over include/block, +# remove the repetive code +# - until loop not supported over include statement (nor block) +# https://github.com/ansible/ansible/issues/17098 +# - with_items not supported over block + +# Check the cluster status for the first time +- include: check_cluster_status.yml + +# Check the cluster status for the second time +- block: + - debug: + msg: "l_etcd_cluster_status_ok: {{ l_etcd_cluster_status_ok }}" + - name: Wait a while before another check + pause: + seconds: 5 + when: not l_etcd_cluster_status_ok | bool + + - include: check_cluster_status.yml + when: not l_etcd_cluster_status_ok | bool + + +# Check the cluster status for the third time +- block: + - debug: + msg: "l_etcd_cluster_status_ok: {{ l_etcd_cluster_status_ok }}" + - name: Wait a while before another check + pause: + seconds: 5 + when: not l_etcd_cluster_status_ok | bool + + - include: check_cluster_status.yml + when: not l_etcd_cluster_status_ok | bool diff --git a/roles/etcd/tasks/migration/check_cluster_health.yml b/roles/etcd/tasks/migration/check_cluster_health.yml new file mode 100644 index 000000000..201d83f99 --- /dev/null +++ b/roles/etcd/tasks/migration/check_cluster_health.yml @@ -0,0 +1,23 @@ +--- +- name: Check cluster health + command: > + etcdctl --cert-file {{ etcd_peer_cert_file }} --key-file {{ etcd_peer_key_file }} --ca-file {{ etcd_peer_ca_file }} --endpoint https://{{ etcd_peer }}:{{ etcd_client_port }} cluster-health + register: etcd_cluster_health + changed_when: false + failed_when: false + +- name: Assume a member is not healthy + set_fact: + etcd_member_healthy: false + +- name: Get member item health status + set_fact: + etcd_member_healthy: true + with_items: "{{ etcd_cluster_health.stdout_lines }}" + when: "(etcd_peer in item) and ('is healthy' in item)" + +- name: Check the etcd cluster health + # TODO(jchaloup): should we fail or ask user if he wants to continue? Or just wait until the cluster is healthy? + fail: + msg: "Etcd member {{ etcd_peer }} is not healthy" + when: not etcd_member_healthy diff --git a/roles/etcd/tasks/migration/check_cluster_status.yml b/roles/etcd/tasks/migration/check_cluster_status.yml new file mode 100644 index 000000000..b69fb5a52 --- /dev/null +++ b/roles/etcd/tasks/migration/check_cluster_status.yml @@ -0,0 +1,32 @@ +--- +# etcd_ip originates from etcd_common role +- name: Check cluster status + command: > + etcdctl --cert {{ etcd_peer_cert_file }} --key {{ etcd_peer_key_file }} --cacert {{ etcd_peer_ca_file }} --endpoints 'https://{{ etcd_peer }}:{{ etcd_client_port }}' -w json endpoint status + environment: + ETCDCTL_API: 3 + register: l_etcd_cluster_status + +- name: Retrieve raftIndex + set_fact: + etcd_member_raft_index: "{{ (l_etcd_cluster_status.stdout | from_json)[0]['Status']['raftIndex'] }}" + +- block: + # http://docs.ansible.com/ansible/playbooks_filters.html#extracting-values-from-containers + - name: Group all raftIndices into a list + set_fact: + etcd_members_raft_indices: "{{ groups['oo_etcd_to_migrate'] | map('extract', hostvars, 'etcd_member_raft_index') | list | unique }}" + + - name: Check the minimum and the maximum of raftIndices is at most 1 + set_fact: + etcd_members_raft_indices_diff: "{{ ((etcd_members_raft_indices | max | int) - (etcd_members_raft_indices | min | int)) | int }}" + + - debug: + msg: "Raft indices difference: {{ etcd_members_raft_indices_diff }}" + + when: inventory_hostname in groups.oo_etcd_to_migrate[0] + +# The cluster raft status is ok if the difference of the max and min raft index is at most 1 +- name: capture the status + set_fact: + l_etcd_cluster_status_ok: "{{ hostvars[groups.oo_etcd_to_migrate[0]]['etcd_members_raft_indices_diff'] | int < 2 }}" diff --git a/roles/etcd/tasks/migration/configure_master.yml b/roles/etcd/tasks/migration/configure_master.yml new file mode 100644 index 000000000..a305d5bf3 --- /dev/null +++ b/roles/etcd/tasks/migration/configure_master.yml @@ -0,0 +1,13 @@ +--- +- name: Configure master to use etcd3 storage backend + yedit: + src: /etc/origin/master/master-config.yaml + key: "{{ item.key }}" + value: "{{ item.value }}" + with_items: + - key: kubernetesMasterConfig.apiServerArguments.storage-backend + value: + - etcd3 + - key: kubernetesMasterConfig.apiServerArguments.storage-media-type + value: + - application/vnd.kubernetes.protobuf diff --git a/roles/etcd/tasks/migration/migrate.yml b/roles/etcd/tasks/migration/migrate.yml new file mode 100644 index 000000000..54a9c74ff --- /dev/null +++ b/roles/etcd/tasks/migration/migrate.yml @@ -0,0 +1,56 @@ +--- +# Should this be run in a serial manner? +- set_fact: + l_etcd_service: "{{ 'etcd_container' if openshift.common.is_containerized else 'etcd' }}" + +- name: Migrate etcd data + command: > + etcdctl migrate --data-dir={{ etcd_data_dir }} + environment: + ETCDCTL_API: 3 + register: l_etcdctl_migrate +# TODO(jchaloup): If any of the members fails, we need to restore all members to v2 from the pre-migrate backup +- name: Check the etcd v2 data are correctly migrated + fail: + msg: "Failed to migrate a member" + when: "'finished transforming keys' not in l_etcdctl_migrate.stdout and 'no v2 keys to migrate' not in l_etcdctl_migrate.stdout" +- name: Migration message + debug: + msg: "Etcd migration finished with: {{ l_etcdctl_migrate.stdout }}" +- name: Set ETCD_FORCE_NEW_CLUSTER=true on first etcd host + lineinfile: + line: "ETCD_FORCE_NEW_CLUSTER=true" + dest: /etc/etcd/etcd.conf + backup: true +- name: Start etcd + systemd: + name: "{{ l_etcd_service }}" + state: started +- name: Wait for cluster to become healthy after bringing up first member + command: > + etcdctl --cert-file {{ etcd_peer_cert_file }} --key-file {{ etcd_peer_key_file }} --ca-file {{ etcd_peer_ca_file }} --endpoint https://{{ etcd_peer }}:{{ etcd_client_port }} cluster-health + register: l_etcd_migrate_health + until: l_etcd_migrate_health.rc == 0 + retries: 3 + delay: 30 +- name: Unset ETCD_FORCE_NEW_CLUSTER=true on first etcd host + lineinfile: + line: "ETCD_FORCE_NEW_CLUSTER=true" + dest: /etc/etcd/etcd.conf + state: absent + backup: true +- name: Restart first etcd host + systemd: + name: "{{ l_etcd_service }}" + state: restarted + +- name: Wait for cluster to become healthy after bringing up first member + command: > + etcdctl --cert-file {{ etcd_peer_cert_file }} --key-file {{ etcd_peer_key_file }} --ca-file {{ etcd_peer_ca_file }} --endpoint https://{{ etcd_peer }}:{{ etcd_client_port }} cluster-health + register: l_etcd_migrate_health + until: l_etcd_migrate_health.rc == 0 + retries: 3 + delay: 30 + +- set_fact: + r_etcd_migrate_success: true diff --git a/roles/etcd/tasks/remove_ca_certificates.yml b/roles/etcd/tasks/remove_ca_certificates.yml new file mode 100644 index 000000000..36df1a1cc --- /dev/null +++ b/roles/etcd/tasks/remove_ca_certificates.yml @@ -0,0 +1,2 @@ +--- +- include: certificates/remove_ca_certificates.yml diff --git a/roles/etcd/tasks/remove_generated_certificates.yml b/roles/etcd/tasks/remove_generated_certificates.yml new file mode 100644 index 000000000..b10a4b32d --- /dev/null +++ b/roles/etcd/tasks/remove_generated_certificates.yml @@ -0,0 +1,2 @@ +--- +- include: certificates/remove_generated_certificates.yml diff --git a/roles/etcd/tasks/retrieve_ca_certificates.yml b/roles/etcd/tasks/retrieve_ca_certificates.yml new file mode 100644 index 000000000..bd6c4ec85 --- /dev/null +++ b/roles/etcd/tasks/retrieve_ca_certificates.yml @@ -0,0 +1,2 @@ +--- +- include: certificates/retrieve_ca_certificates.yml diff --git a/roles/etcd/tasks/server_certificates.yml b/roles/etcd/tasks/server_certificates.yml new file mode 100644 index 000000000..ae26079f9 --- /dev/null +++ b/roles/etcd/tasks/server_certificates.yml @@ -0,0 +1,6 @@ +--- +- include: ca.yml + when: + - etcd_ca_setup | default(True) | bool + +- include: certificates/fetch_server_certificates_from_ca.yml diff --git a/roles/etcd/tasks/system_container.yml b/roles/etcd/tasks/system_container.yml new file mode 100644 index 000000000..024479fb4 --- /dev/null +++ b/roles/etcd/tasks/system_container.yml @@ -0,0 +1,100 @@ +--- +- set_fact: + l_etcd_src_data_dir: "{{ '/var/lib/origin/openshift.local.etcd' if r_etcd_common_embedded_etcd | bool else '/var/lib/etcd/' }}" + +- name: Pull etcd system container + command: atomic pull --storage=ostree {{ openshift.etcd.etcd_image }} + register: pull_result + changed_when: "'Pulling layer' in pull_result.stdout" + +- name: Set initial Etcd cluster + set_fact: + etcd_initial_cluster: >- + {% for host in etcd_peers | default([]) -%} + {% if loop.last -%} + {{ hostvars[host].etcd_hostname }}={{ etcd_peer_url_scheme }}://{{ hostvars[host].etcd_ip }}:{{ etcd_peer_port }} + {%- else -%} + {{ hostvars[host].etcd_hostname }}={{ etcd_peer_url_scheme }}://{{ hostvars[host].etcd_ip }}:{{ etcd_peer_port }}, + {%- endif -%} + {% endfor -%} + when: etcd_initial_cluster is undefined + +- name: Check etcd system container package + command: > + atomic containers list --no-trunc -a -f container=etcd -f backend=ostree + register: etcd_result + +- name: Unmask etcd service + systemd: + name: etcd + state: stopped + enabled: no + masked: no + daemon_reload: yes + register: task_result + failed_when: task_result|failed and 'could not' not in task_result.msg|lower + when: "'etcd' not in etcd_result.stdout" + +- name: Disable etcd_container + systemd: + name: etcd_container + state: stopped + enabled: no + daemon_reload: yes + register: task_result + failed_when: task_result|failed and 'could not' not in task_result.msg|lower + +- name: Remove etcd_container.service + file: + path: /etc/systemd/system/etcd_container.service + state: absent + +- name: Systemd reload configuration + systemd: name=etcd_container daemon_reload=yes + +- name: Check for previous etcd data store + stat: + path: "{{ l_etcd_src_data_dir }}/member/" + register: src_datastore + +- name: Check for etcd system container data store + stat: + path: "{{ r_etcd_common_system_container_host_dir }}/etcd.etcd/member" + register: dest_datastore + +- name: Ensure that etcd system container data dirs exist + file: path="{{ item }}" state=directory + with_items: + - "{{ r_etcd_common_system_container_host_dir }}/etc" + - "{{ r_etcd_common_system_container_host_dir }}/etcd.etcd" + +- name: Copy etcd data store + command: > + cp -a {{ l_etcd_src_data_dir }}/member + {{ r_etcd_common_system_container_host_dir }}/etcd.etcd/member + when: + - src_datastore.stat.exists + - not dest_datastore.stat.exists + +- name: Install or Update Etcd system container package + oc_atomic_container: + name: etcd + image: "{{ openshift.etcd.etcd_image }}" + state: latest + values: + - ETCD_LISTEN_PEER_URLS={{ etcd_listen_peer_urls }} + - ETCD_NAME={{ etcd_hostname }} + - ETCD_INITIAL_CLUSTER={{ etcd_initial_cluster }} + - ETCD_LISTEN_CLIENT_URLS={{ etcd_listen_client_urls }} + - ETCD_INITIAL_ADVERTISE_PEER_URLS={{ etcd_initial_advertise_peer_urls }} + - ETCD_INITIAL_CLUSTER_STATE={{ etcd_initial_cluster_state }} + - ETCD_INITIAL_CLUSTER_TOKEN={{ etcd_initial_cluster_token }} + - ETCD_ADVERTISE_CLIENT_URLS={{ etcd_advertise_client_urls }} + - ETCD_CA_FILE={{ etcd_system_container_conf_dir }}/ca.crt + - ETCD_CERT_FILE={{ etcd_system_container_conf_dir }}/server.crt + - ETCD_KEY_FILE={{ etcd_system_container_conf_dir }}/server.key + - ETCD_PEER_CA_FILE={{ etcd_system_container_conf_dir }}/ca.crt + - ETCD_PEER_CERT_FILE={{ etcd_system_container_conf_dir }}/peer.crt + - ETCD_PEER_KEY_FILE={{ etcd_system_container_conf_dir }}/peer.key + - ETCD_TRUSTED_CA_FILE={{ etcd_system_container_conf_dir }}/ca.crt + - ETCD_PEER_TRUSTED_CA_FILE={{ etcd_system_container_conf_dir }}/ca.crt diff --git a/roles/etcd/tasks/upgrade/upgrade_image.yml b/roles/etcd/tasks/upgrade/upgrade_image.yml new file mode 100644 index 000000000..24071f9ad --- /dev/null +++ b/roles/etcd/tasks/upgrade/upgrade_image.yml @@ -0,0 +1,60 @@ +--- +# INPUT r_etcd_upgrade_version +- name: Verify cluster is healthy pre-upgrade + command: "{{ etcdctlv2 }} cluster-health" + +- name: Get current image + shell: "grep 'ExecStart=' {{ etcd_service_file }} | awk '{print $NF}'" + register: current_image + +- name: Set new_etcd_image + set_fact: + new_etcd_image: "{{ current_image.stdout | regex_replace('/etcd.*$','/etcd:' ~ r_etcd_upgrade_version ) }}" + +- name: Pull new etcd image + command: "docker pull {{ new_etcd_image }}" + +- name: Update to latest etcd image + replace: + dest: "{{ etcd_service_file }}" + regexp: "{{ current_image.stdout }}$" + replace: "{{ new_etcd_image }}" + +- lineinfile: + destfile: "{{ etcd_conf_file }}" + regexp: '^ETCD_QUOTA_BACKEND_BYTES=' + line: "ETCD_QUOTA_BACKEND_BYTES={{ etcd_quota_backend_bytes }}" + +- name: Restart etcd_container + systemd: + name: "{{ etcd_service }}" + daemon_reload: yes + state: restarted + +## TODO: probably should just move this into the backup playbooks, also this +## will fail on atomic host. We need to revisit how to do etcd backups there as +## the container may be newer than etcdctl on the host. Assumes etcd3 obsoletes etcd (7.3.1) +- name: Detecting Atomic Host Operating System + stat: + path: /run/ostree-booted + register: l_ostree_booted + +- name: Upgrade etcd for etcdctl when not atomic + package: + name: etcd + state: latest + when: not l_ostree_booted.stat.exists | bool + +- name: Verify cluster is healthy + command: "{{ etcdctlv2 }} cluster-health" + register: etcdctl + until: etcdctl.rc == 0 + retries: 3 + delay: 10 + +- name: Store new etcd_image + # DEPENDENCY openshift_facts + openshift_facts: + role: etcd + local_facts: + etcd_image: "{{ new_etcd_image }}" diff --git a/roles/etcd/tasks/upgrade/upgrade_rpm.yml b/roles/etcd/tasks/upgrade/upgrade_rpm.yml new file mode 100644 index 000000000..505e28afb --- /dev/null +++ b/roles/etcd/tasks/upgrade/upgrade_rpm.yml @@ -0,0 +1,37 @@ +--- +# INPUT r_etcd_upgrade_version? + +# F23 GA'd with etcd 2.0, currently has 2.2 in updates +# F24 GA'd with etcd-2.2, currently has 2.2 in updates +# F25 Beta currently has etcd 3.0 +# RHEL 7.3.4 with etcd-3.1.3-1.el7 +# RHEL 7.3.3 with etcd-3.1.0-2.el7 +# RHEL 7.3.2 with etcd-3.0.15-1.el7 + +- name: Verify cluster is healthy pre-upgrade + command: "{{ etcdctlv2 }} cluster-health" + +- set_fact: + l_etcd_target_package: "{{ 'etcd' if r_etcd_upgrade_version is not defined else 'etcd-'+r_etcd_upgrade_version+'*' }}" + +- name: Update etcd RPM to {{ l_etcd_target_package }} + package: + name: "{{ l_etcd_target_package }}" + state: latest + +- lineinfile: + destfile: "{{ etcd_conf_file }}" + regexp: '^ETCD_QUOTA_BACKEND_BYTES=' + line: "ETCD_QUOTA_BACKEND_BYTES={{ etcd_quota_backend_bytes }}" + +- name: Restart etcd + service: + name: "{{ etcd_service }}" + state: restarted + +- name: Verify cluster is healthy + command: "{{ etcdctlv2 }} cluster-health" + register: etcdctl + until: etcdctl.rc == 0 + retries: 3 + delay: 10 diff --git a/roles/etcd/tasks/upgrade_image.yml b/roles/etcd/tasks/upgrade_image.yml new file mode 100644 index 000000000..9e69027eb --- /dev/null +++ b/roles/etcd/tasks/upgrade_image.yml @@ -0,0 +1,2 @@ +--- +- include: upgrade/upgrade_image.yml diff --git a/roles/etcd/tasks/upgrade_rpm.yml b/roles/etcd/tasks/upgrade_rpm.yml new file mode 100644 index 000000000..29603d2b6 --- /dev/null +++ b/roles/etcd/tasks/upgrade_rpm.yml @@ -0,0 +1,2 @@ +--- +- include: upgrade/upgrade_rpm.yml |