summaryrefslogtreecommitdiffstats
path: root/roles/etcd
diff options
context:
space:
mode:
Diffstat (limited to 'roles/etcd')
-rw-r--r--roles/etcd/defaults/main.yaml82
-rwxr-xr-xroles/etcd/library/delegated_serial_command.py274
-rw-r--r--roles/etcd/meta/main.yml10
-rw-r--r--roles/etcd/tasks/auxiliary/clean_data.yml5
-rw-r--r--roles/etcd/tasks/auxiliary/drop_etcdctl.yml12
-rw-r--r--roles/etcd/tasks/backup.yml2
-rw-r--r--roles/etcd/tasks/backup/backup.yml101
-rw-r--r--roles/etcd/tasks/backup_ca_certificates.yml2
-rw-r--r--roles/etcd/tasks/backup_generated_certificates.yml2
-rw-r--r--roles/etcd/tasks/backup_server_certificates.yml2
-rw-r--r--roles/etcd/tasks/ca.yml2
-rw-r--r--roles/etcd/tasks/certificates/backup_ca_certificates.yml12
-rw-r--r--roles/etcd/tasks/certificates/backup_generated_certificates.yml13
-rw-r--r--roles/etcd/tasks/certificates/backup_server_certificates.yml11
-rw-r--r--roles/etcd/tasks/certificates/deploy_ca.yml78
-rw-r--r--roles/etcd/tasks/certificates/distribute_ca.yml47
-rw-r--r--roles/etcd/tasks/certificates/fetch_client_certificates_from_ca.yml138
-rw-r--r--roles/etcd/tasks/certificates/fetch_server_certificates_from_ca.yml234
-rw-r--r--roles/etcd/tasks/certificates/remove_ca_certificates.yml5
-rw-r--r--roles/etcd/tasks/certificates/remove_generated_certificates.yml5
-rw-r--r--roles/etcd/tasks/certificates/retrieve_ca_certificates.yml8
-rw-r--r--roles/etcd/tasks/clean_data.yml2
-rw-r--r--roles/etcd/tasks/client_certificates.yml2
-rw-r--r--roles/etcd/tasks/distribute_ca2
-rw-r--r--roles/etcd/tasks/drop_etcdctl.yml2
-rw-r--r--roles/etcd/tasks/firewall.yml40
-rw-r--r--roles/etcd/tasks/main.yml14
-rw-r--r--roles/etcd/tasks/migrate.add_ttls.yml2
-rw-r--r--roles/etcd/tasks/migrate.configure_master.yml2
-rw-r--r--roles/etcd/tasks/migrate.pre_check.yml2
-rw-r--r--roles/etcd/tasks/migrate.yml2
-rw-r--r--roles/etcd/tasks/migration/add_ttls.yml34
-rw-r--r--roles/etcd/tasks/migration/check.yml56
-rw-r--r--roles/etcd/tasks/migration/check_cluster_health.yml23
-rw-r--r--roles/etcd/tasks/migration/check_cluster_status.yml32
-rw-r--r--roles/etcd/tasks/migration/configure_master.yml13
-rw-r--r--roles/etcd/tasks/migration/migrate.yml56
-rw-r--r--roles/etcd/tasks/remove_ca_certificates.yml2
-rw-r--r--roles/etcd/tasks/remove_generated_certificates.yml2
-rw-r--r--roles/etcd/tasks/retrieve_ca_certificates.yml2
-rw-r--r--roles/etcd/tasks/server_certificates.yml6
-rw-r--r--roles/etcd/tasks/upgrade/upgrade_image.yml60
-rw-r--r--roles/etcd/tasks/upgrade/upgrade_rpm.yml37
-rw-r--r--roles/etcd/tasks/upgrade_image.yml2
-rw-r--r--roles/etcd/tasks/upgrade_rpm.yml2
-rw-r--r--roles/etcd/templates/etcd.conf.j245
-rw-r--r--roles/etcd/templates/etcdctl.sh.j212
-rw-r--r--roles/etcd/templates/openssl_append.j251
48 files changed, 1523 insertions, 27 deletions
diff --git a/roles/etcd/defaults/main.yaml b/roles/etcd/defaults/main.yaml
index c0d1d5946..18164050a 100644
--- a/roles/etcd/defaults/main.yaml
+++ b/roles/etcd/defaults/main.yaml
@@ -1,4 +1,67 @@
---
+r_etcd_common_backup_tag: ''
+r_etcd_common_backup_sufix_name: ''
+
+# runc, docker, host
+r_etcd_common_etcd_runtime: "docker"
+r_etcd_common_embedded_etcd: false
+
+# etcd run on a host => use etcdctl command directly
+# etcd run as a docker container => use docker exec
+# etcd run as a runc container => use runc exec
+r_etcd_common_etcdctl_command: "{{ 'etcdctl' if r_etcd_common_etcd_runtime == 'host' or r_etcd_common_embedded_etcd | bool else 'docker exec etcd_container etcdctl' if r_etcd_common_etcd_runtime == 'docker' else 'runc exec etcd etcdctl' }}"
+
+# etcd server vars
+etcd_conf_dir: '/etc/etcd'
+r_etcd_common_system_container_host_dir: /var/lib/etcd/etcd.etcd
+etcd_system_container_conf_dir: /var/lib/etcd/etc
+etcd_conf_file: "{{ etcd_conf_dir }}/etcd.conf"
+etcd_ca_file: "{{ etcd_conf_dir }}/ca.crt"
+etcd_cert_file: "{{ etcd_conf_dir }}/server.crt"
+etcd_key_file: "{{ etcd_conf_dir }}/server.key"
+etcd_peer_ca_file: "{{ etcd_conf_dir }}/ca.crt"
+etcd_peer_cert_file: "{{ etcd_conf_dir }}/peer.crt"
+etcd_peer_key_file: "{{ etcd_conf_dir }}/peer.key"
+
+# etcd ca vars
+etcd_ca_dir: "{{ etcd_conf_dir}}/ca"
+etcd_generated_certs_dir: "{{ etcd_conf_dir }}/generated_certs"
+etcd_ca_cert: "{{ etcd_ca_dir }}/ca.crt"
+etcd_ca_key: "{{ etcd_ca_dir }}/ca.key"
+etcd_openssl_conf: "{{ etcd_ca_dir }}/openssl.cnf"
+etcd_ca_name: etcd_ca
+etcd_req_ext: etcd_v3_req
+etcd_ca_exts_peer: etcd_v3_ca_peer
+etcd_ca_exts_server: etcd_v3_ca_server
+etcd_ca_exts_self: etcd_v3_ca_self
+etcd_ca_exts_client: etcd_v3_ca_client
+etcd_ca_crl_dir: "{{ etcd_ca_dir }}/crl"
+etcd_ca_new_certs_dir: "{{ etcd_ca_dir }}/certs"
+etcd_ca_db: "{{ etcd_ca_dir }}/index.txt"
+etcd_ca_serial: "{{ etcd_ca_dir }}/serial"
+etcd_ca_crl_number: "{{ etcd_ca_dir }}/crlnumber"
+etcd_ca_default_days: 1825
+
+r_etcd_common_master_peer_cert_file: /etc/origin/master/master.etcd-client.crt
+r_etcd_common_master_peer_key_file: /etc/origin/master/master.etcd-client.key
+r_etcd_common_master_peer_ca_file: /etc/origin/master/master.etcd-ca.crt
+
+# etcd server & certificate vars
+etcd_hostname: "{{ inventory_hostname }}"
+etcd_ip: "{{ ansible_default_ipv4.address }}"
+etcd_is_atomic: False
+etcd_is_containerized: False
+etcd_is_thirdparty: False
+
+# etcd dir vars
+etcd_data_dir: "{{ '/var/lib/origin/openshift.local.etcd' if r_etcd_common_embedded_etcd | bool else '/var/lib/etcd/' if r_etcd_common_etcd_runtime != 'runc' else '/var/lib/etcd/etcd.etcd/' }}"
+
+# etcd ports and protocols
+etcd_client_port: 2379
+etcd_peer_port: 2380
+etcd_url_scheme: http
+etcd_peer_url_scheme: http
+
etcd_initial_cluster_state: new
etcd_initial_cluster_token: etcd-cluster-1
@@ -7,4 +70,23 @@ etcd_listen_peer_urls: "{{ etcd_peer_url_scheme }}://{{ etcd_ip }}:{{ etcd_peer_
etcd_advertise_client_urls: "{{ etcd_url_scheme }}://{{ etcd_ip }}:{{ etcd_client_port }}"
etcd_listen_client_urls: "{{ etcd_url_scheme }}://{{ etcd_ip }}:{{ etcd_client_port }}"
+etcd_peer: 127.0.0.1
+etcdctlv2: "etcdctl --cert-file {{ etcd_peer_cert_file }} --key-file {{ etcd_peer_key_file }} --ca-file {{ etcd_peer_ca_file }} -C https://{{ etcd_peer }}:{{ etcd_client_port }}"
+
+etcd_service: "{{ 'etcd_container' if r_etcd_common_etcd_runtime == 'docker' else 'etcd' }}"
+# Location of the service file is fixed and not meant to be changed
+etcd_service_file: "/etc/systemd/system/{{ etcd_service }}.service"
+
+r_etcd_firewall_enabled: "{{ os_firewall_enabled | default(True) }}"
+r_etcd_use_firewalld: "{{ os_firewall_use_firewalld | default(Falsel) }}"
+
etcd_systemd_dir: "/etc/systemd/system/{{ etcd_service }}.service.d"
+r_etcd_os_firewall_deny: []
+r_etcd_os_firewall_allow:
+- service: etcd
+ port: "{{etcd_client_port}}/tcp"
+- service: etcd peering
+ port: "{{ etcd_peer_port }}/tcp"
+
+# set the backend quota to 4GB by default
+etcd_quota_backend_bytes: 4294967296
diff --git a/roles/etcd/library/delegated_serial_command.py b/roles/etcd/library/delegated_serial_command.py
new file mode 100755
index 000000000..0cab1ca88
--- /dev/null
+++ b/roles/etcd/library/delegated_serial_command.py
@@ -0,0 +1,274 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>, and others
+# (c) 2016, Andrew Butcher <abutcher@redhat.com>
+#
+# This module is derrived from the Ansible command module.
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+
+# pylint: disable=unused-wildcard-import,wildcard-import,unused-import,redefined-builtin
+
+''' delegated_serial_command '''
+
+import datetime
+import errno
+import glob
+import shlex
+import os
+import fcntl
+import time
+
+DOCUMENTATION = '''
+---
+module: delegated_serial_command
+short_description: Executes a command on a remote node
+version_added: historical
+description:
+ - The M(command) module takes the command name followed by a list
+ of space-delimited arguments.
+ - The given command will be executed on all selected nodes. It
+ will not be processed through the shell, so variables like
+ C($HOME) and operations like C("<"), C(">"), C("|"), and C("&")
+ will not work (use the M(shell) module if you need these
+ features).
+ - Creates and maintains a lockfile such that this module will
+ wait for other invocations to proceed.
+options:
+ command:
+ description:
+ - the command to run
+ required: true
+ default: null
+ creates:
+ description:
+ - a filename or (since 2.0) glob pattern, when it already
+ exists, this step will B(not) be run.
+ required: no
+ default: null
+ removes:
+ description:
+ - a filename or (since 2.0) glob pattern, when it does not
+ exist, this step will B(not) be run.
+ version_added: "0.8"
+ required: no
+ default: null
+ chdir:
+ description:
+ - cd into this directory before running the command
+ version_added: "0.6"
+ required: false
+ default: null
+ executable:
+ description:
+ - change the shell used to execute the command. Should be an
+ absolute path to the executable.
+ required: false
+ default: null
+ version_added: "0.9"
+ warn:
+ version_added: "1.8"
+ default: yes
+ description:
+ - if command warnings are on in ansible.cfg, do not warn about
+ this particular line if set to no/false.
+ required: false
+ lockfile:
+ default: yes
+ description:
+ - the lockfile that will be created
+ timeout:
+ default: yes
+ description:
+ - time in milliseconds to wait to obtain the lock
+notes:
+ - If you want to run a command through the shell (say you are using C(<),
+ C(>), C(|), etc), you actually want the M(shell) module instead. The
+ M(command) module is much more secure as it's not affected by the user's
+ environment.
+ - " C(creates), C(removes), and C(chdir) can be specified after
+ the command. For instance, if you only want to run a command if
+ a certain file does not exist, use this."
+author:
+ - Ansible Core Team
+ - Michael DeHaan
+ - Andrew Butcher
+'''
+
+EXAMPLES = '''
+# Example from Ansible Playbooks.
+- delegated_serial_command:
+ command: /sbin/shutdown -t now
+
+# Run the command if the specified file does not exist.
+- delegated_serial_command:
+ command: /usr/bin/make_database.sh arg1 arg2
+ creates: /path/to/database
+'''
+
+# Dict of options and their defaults
+OPTIONS = {'chdir': None,
+ 'creates': None,
+ 'command': None,
+ 'executable': None,
+ 'NO_LOG': None,
+ 'removes': None,
+ 'warn': True,
+ 'lockfile': None,
+ 'timeout': None}
+
+
+def check_command(commandline):
+ ''' Check provided command '''
+ arguments = {'chown': 'owner', 'chmod': 'mode', 'chgrp': 'group',
+ 'ln': 'state=link', 'mkdir': 'state=directory',
+ 'rmdir': 'state=absent', 'rm': 'state=absent', 'touch': 'state=touch'}
+ commands = {'git': 'git', 'hg': 'hg', 'curl': 'get_url or uri', 'wget': 'get_url or uri',
+ 'svn': 'subversion', 'service': 'service',
+ 'mount': 'mount', 'rpm': 'yum, dnf or zypper', 'yum': 'yum', 'apt-get': 'apt',
+ 'tar': 'unarchive', 'unzip': 'unarchive', 'sed': 'template or lineinfile',
+ 'rsync': 'synchronize', 'dnf': 'dnf', 'zypper': 'zypper'}
+ become = ['sudo', 'su', 'pbrun', 'pfexec', 'runas']
+ warnings = list()
+ command = os.path.basename(commandline.split()[0])
+ # pylint: disable=line-too-long
+ if command in arguments:
+ warnings.append("Consider using file module with {0} rather than running {1}".format(arguments[command], command))
+ if command in commands:
+ warnings.append("Consider using {0} module rather than running {1}".format(commands[command], command))
+ if command in become:
+ warnings.append(
+ "Consider using 'become', 'become_method', and 'become_user' rather than running {0}".format(command,))
+ return warnings
+
+
+# pylint: disable=too-many-statements,too-many-branches,too-many-locals
+def main():
+ ''' Main module function '''
+ module = AnsibleModule( # noqa: F405
+ argument_spec=dict(
+ _uses_shell=dict(type='bool', default=False),
+ command=dict(required=True),
+ chdir=dict(),
+ executable=dict(),
+ creates=dict(),
+ removes=dict(),
+ warn=dict(type='bool', default=True),
+ lockfile=dict(default='/tmp/delegated_serial_command.lock'),
+ timeout=dict(type='int', default=30)
+ )
+ )
+
+ shell = module.params['_uses_shell']
+ chdir = module.params['chdir']
+ executable = module.params['executable']
+ command = module.params['command']
+ creates = module.params['creates']
+ removes = module.params['removes']
+ warn = module.params['warn']
+ lockfile = module.params['lockfile']
+ timeout = module.params['timeout']
+
+ if command.strip() == '':
+ module.fail_json(rc=256, msg="no command given")
+
+ iterated = 0
+ lockfd = open(lockfile, 'w+')
+ while iterated < timeout:
+ try:
+ fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ break
+ # pylint: disable=invalid-name
+ except IOError as e:
+ if e.errno != errno.EAGAIN:
+ module.fail_json(msg="I/O Error {0}: {1}".format(e.errno, e.strerror))
+ else:
+ iterated += 1
+ time.sleep(0.1)
+
+ if chdir:
+ chdir = os.path.abspath(os.path.expanduser(chdir))
+ os.chdir(chdir)
+
+ if creates:
+ # do not run the command if the line contains creates=filename
+ # and the filename already exists. This allows idempotence
+ # of command executions.
+ path = os.path.expanduser(creates)
+ if glob.glob(path):
+ module.exit_json(
+ cmd=command,
+ stdout="skipped, since %s exists" % path,
+ changed=False,
+ stderr=False,
+ rc=0
+ )
+
+ if removes:
+ # do not run the command if the line contains removes=filename
+ # and the filename does not exist. This allows idempotence
+ # of command executions.
+ path = os.path.expanduser(removes)
+ if not glob.glob(path):
+ module.exit_json(
+ cmd=command,
+ stdout="skipped, since %s does not exist" % path,
+ changed=False,
+ stderr=False,
+ rc=0
+ )
+
+ warnings = list()
+ if warn:
+ warnings = check_command(command)
+
+ if not shell:
+ command = shlex.split(command)
+ startd = datetime.datetime.now()
+
+ # pylint: disable=invalid-name
+ rc, out, err = module.run_command(command, executable=executable, use_unsafe_shell=shell)
+
+ fcntl.flock(lockfd, fcntl.LOCK_UN)
+ lockfd.close()
+
+ endd = datetime.datetime.now()
+ delta = endd - startd
+
+ if out is None:
+ out = ''
+ if err is None:
+ err = ''
+
+ module.exit_json(
+ cmd=command,
+ stdout=out.rstrip("\r\n"),
+ stderr=err.rstrip("\r\n"),
+ rc=rc,
+ start=str(startd),
+ end=str(endd),
+ delta=str(delta),
+ changed=True,
+ warnings=warnings,
+ iterated=iterated
+ )
+
+
+# import module snippets
+# pylint: disable=wrong-import-position
+from ansible.module_utils.basic import * # noqa: F402,F403
+
+main()
diff --git a/roles/etcd/meta/main.yml b/roles/etcd/meta/main.yml
index 689c07a84..879ca4f4e 100644
--- a/roles/etcd/meta/main.yml
+++ b/roles/etcd/meta/main.yml
@@ -17,11 +17,5 @@ galaxy_info:
- system
dependencies:
- role: lib_openshift
-- role: os_firewall
- os_firewall_allow:
- - service: etcd
- port: "{{etcd_client_port}}/tcp"
- - service: etcd peering
- port: "{{ etcd_peer_port }}/tcp"
-- role: etcd_server_certificates
-- role: etcd_common
+- role: lib_os_firewall
+- role: lib_utils
diff --git a/roles/etcd/tasks/auxiliary/clean_data.yml b/roles/etcd/tasks/auxiliary/clean_data.yml
new file mode 100644
index 000000000..95a0e7c0a
--- /dev/null
+++ b/roles/etcd/tasks/auxiliary/clean_data.yml
@@ -0,0 +1,5 @@
+---
+- name: Remove member data
+ file:
+ path: /var/lib/etcd/member
+ state: absent
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/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/backup.yml b/roles/etcd/tasks/backup/backup.yml
new file mode 100644
index 000000000..42d27c081
--- /dev/null
+++ b/roles/etcd/tasks/backup/backup.yml
@@ -0,0 +1,101 @@
+---
+# 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 }}"
+
+# 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_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_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_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/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/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/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
index f0661209f..f643d292d 100644
--- a/roles/etcd/tasks/main.yml
+++ b/roles/etcd/tasks/main.yml
@@ -1,4 +1,6 @@
---
+- include: server_certificates.yml
+
- name: Set hostname and ip facts
set_fact:
# Store etcd_hostname and etcd_ip such that they will be available
@@ -6,15 +8,17 @@
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_role:
- name: etcd_common
- vars:
- r_etcd_common_action: drop_etcdctl
- when: openshift_etcd_etcdctl_profile | default(true) | bool
+- include: drop_etcdctl.yml
+ when:
+ - openshift_etcd_etcdctl_profile | default(true) | bool
- block:
- name: Pull etcd container
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/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
diff --git a/roles/etcd/templates/etcd.conf.j2 b/roles/etcd/templates/etcd.conf.j2
index 1b5598f46..8462bb4c8 100644
--- a/roles/etcd/templates/etcd.conf.j2
+++ b/roles/etcd/templates/etcd.conf.j2
@@ -8,14 +8,11 @@
{% endfor -%}
{% endmacro -%}
-{% if (etcd_peers | default([]) | length > 1) or (etcd_is_thirdparty) %}
ETCD_NAME={{ etcd_hostname }}
ETCD_LISTEN_PEER_URLS={{ etcd_listen_peer_urls }}
-{% else %}
-ETCD_NAME=default
-{% endif %}
ETCD_DATA_DIR={{ etcd_data_dir }}
-#ETCD_SNAPSHOT_COUNTER=10000
+#ETCD_WAL_DIR=""
+#ETCD_SNAPSHOT_COUNT=10000
ETCD_HEARTBEAT_INTERVAL=500
ETCD_ELECTION_TIMEOUT=2500
ETCD_LISTEN_CLIENT_URLS={{ etcd_listen_client_urls }}
@@ -23,20 +20,20 @@ ETCD_LISTEN_CLIENT_URLS={{ etcd_listen_client_urls }}
#ETCD_MAX_WALS=5
#ETCD_CORS=
-{% if etcd_is_thirdparty %}
+
#[cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS={{ etcd_initial_advertise_peer_urls }}
-
+{% if etcd_is_thirdparty %}
# TODO: This needs to be altered to support the correct etcd instances
ETCD_INITIAL_CLUSTER={{ etcd_hostname}}={{ etcd_initial_advertise_peer_urls }}
ETCD_INITIAL_CLUSTER_STATE={{ etcd_initial_cluster_state }}
ETCD_INITIAL_CLUSTER_TOKEN=thirdparty-etcd-cluster-1
-{% endif %}
-
-{% if etcd_peers | default([]) | length > 1 %}
-#[cluster]
-ETCD_INITIAL_ADVERTISE_PEER_URLS={{ etcd_initial_advertise_peer_urls }}
+{% else %}
+{% if initial_etcd_cluster is defined and initial_etcd_cluster %}
+ETCD_INITIAL_CLUSTER={{ initial_etcd_cluster }}
+{% else %}
ETCD_INITIAL_CLUSTER={{ initial_cluster() }}
+{% endif %}
ETCD_INITIAL_CLUSTER_STATE={{ etcd_initial_cluster_state }}
ETCD_INITIAL_CLUSTER_TOKEN={{ etcd_initial_cluster_token }}
#ETCD_DISCOVERY=
@@ -45,24 +42,44 @@ ETCD_INITIAL_CLUSTER_TOKEN={{ etcd_initial_cluster_token }}
#ETCD_DISCOVERY_PROXY=
{% endif %}
ETCD_ADVERTISE_CLIENT_URLS={{ etcd_advertise_client_urls }}
+#ETCD_STRICT_RECONFIG_CHECK="false"
+#ETCD_AUTO_COMPACTION_RETENTION="0"
+#ETCD_ENABLE_V2="true"
+ETCD_QUOTA_BACKEND_BYTES={{ etcd_quota_backend_bytes }}
#[proxy]
#ETCD_PROXY=off
+#ETCD_PROXY_FAILURE_WAIT="5000"
+#ETCD_PROXY_REFRESH_INTERVAL="30000"
+#ETCD_PROXY_DIAL_TIMEOUT="1000"
+#ETCD_PROXY_WRITE_TIMEOUT="5000"
+#ETCD_PROXY_READ_TIMEOUT="0"
#[security]
{% if etcd_url_scheme == 'https' -%}
-ETCD_CA_FILE={{ etcd_ca_file }}
+ETCD_TRUSTED_CA_FILE={{ etcd_ca_file }}
+ETCD_CLIENT_CERT_AUTH="true"
ETCD_CERT_FILE={{ etcd_cert_file }}
ETCD_KEY_FILE={{ etcd_key_file }}
{% endif -%}
+#ETCD_AUTO_TLS="false"
{% if etcd_peer_url_scheme == 'https' -%}
-ETCD_PEER_CA_FILE={{ etcd_peer_ca_file }}
+ETCD_PEER_TRUSTED_CA_FILE={{ etcd_peer_ca_file }}
+ETCD_PEER_CLIENT_CERT_AUTH="true"
ETCD_PEER_CERT_FILE={{ etcd_peer_cert_file }}
ETCD_PEER_KEY_FILE={{ etcd_peer_key_file }}
{% endif -%}
+#ETCD_PEER_AUTO_TLS="false"
#[logging]
ETCD_DEBUG="{{ etcd_debug | default(false) | bool | string }}"
{% if etcd_log_package_levels is defined %}
ETCD_LOG_PACKAGE_LEVELS="{{ etcd_log_package_levels }}"
{% endif %}
+
+#[profiling]
+#ETCD_ENABLE_PPROF="false"
+#ETCD_METRICS="basic"
+#
+#[auth]
+#ETCD_AUTH_TOKEN="simple"
diff --git a/roles/etcd/templates/etcdctl.sh.j2 b/roles/etcd/templates/etcdctl.sh.j2
new file mode 100644
index 000000000..ac7d9c72f
--- /dev/null
+++ b/roles/etcd/templates/etcdctl.sh.j2
@@ -0,0 +1,12 @@
+#!/bin/bash
+# Sets up handy aliases for etcd, need etcdctl2 and etcdctl3 because
+# command flags are different between the two. Should work on stand
+# alone etcd hosts and master + etcd hosts too because we use the peer keys.
+etcdctl2() {
+ /usr/bin/etcdctl --cert-file {{ etcd_peer_cert_file }} --key-file {{ etcd_peer_key_file }} --ca-file {{ etcd_peer_ca_file }} -C https://`hostname`:2379 ${@}
+
+}
+
+etcdctl3() {
+ ETCDCTL_API=3 /usr/bin/etcdctl --cert {{ etcd_peer_cert_file }} --key {{ etcd_peer_key_file }} --cacert {{ etcd_peer_ca_file }} --endpoints https://`hostname`:2379 ${@}
+}
diff --git a/roles/etcd/templates/openssl_append.j2 b/roles/etcd/templates/openssl_append.j2
new file mode 100644
index 000000000..f28316fc2
--- /dev/null
+++ b/roles/etcd/templates/openssl_append.j2
@@ -0,0 +1,51 @@
+
+[ {{ etcd_req_ext }} ]
+basicConstraints = critical,CA:FALSE
+keyUsage = digitalSignature,keyEncipherment
+subjectAltName = ${ENV::SAN}
+
+[ {{ etcd_ca_name }} ]
+dir = {{ etcd_ca_dir }}
+crl_dir = {{ etcd_ca_crl_dir }}
+database = {{ etcd_ca_db }}
+new_certs_dir = {{ etcd_ca_new_certs_dir }}
+certificate = {{ etcd_ca_cert }}
+serial = {{ etcd_ca_serial }}
+private_key = {{ etcd_ca_key }}
+crl_number = {{ etcd_ca_crl_number }}
+x509_extensions = {{ etcd_ca_exts_client }}
+default_days = {{ etcd_ca_default_days }}
+default_md = sha256
+preserve = no
+name_opt = ca_default
+cert_opt = ca_default
+policy = policy_anything
+unique_subject = no
+copy_extensions = copy
+
+[ {{ etcd_ca_exts_self }} ]
+authorityKeyIdentifier = keyid,issuer
+basicConstraints = critical,CA:TRUE,pathlen:0
+keyUsage = critical,digitalSignature,keyEncipherment,keyCertSign
+subjectKeyIdentifier = hash
+
+[ {{ etcd_ca_exts_peer }} ]
+authorityKeyIdentifier = keyid,issuer:always
+basicConstraints = critical,CA:FALSE
+extendedKeyUsage = clientAuth,serverAuth
+keyUsage = digitalSignature,keyEncipherment
+subjectKeyIdentifier = hash
+
+[ {{ etcd_ca_exts_server }} ]
+authorityKeyIdentifier = keyid,issuer:always
+basicConstraints = critical,CA:FALSE
+extendedKeyUsage = serverAuth
+keyUsage = digitalSignature,keyEncipherment
+subjectKeyIdentifier = hash
+
+[ {{ etcd_ca_exts_client }} ]
+authorityKeyIdentifier = keyid,issuer:always
+basicConstraints = critical,CA:FALSE
+extendedKeyUsage = clientAuth
+keyUsage = digitalSignature,keyEncipherment
+subjectKeyIdentifier = hash