From d67c5b8f79609d2d3b07cc009f58e3dc988782c5 Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Mon, 23 Mar 2015 16:30:49 -0400 Subject: node registration changes - Remove default value for openshift_hostname and make it required - Remove workarounds that are no longer needed - Remove resources parameter from openshift_register_node module - pre-create node certificates for each node before registering node - distribute created node certificates to each node - Move node registration logic to a new openshift_register_nodes role - This is because we now have to run the steps on a master as opposed to on the nodes like we were previously doing. - Rename openshift_register_node module to kubernetes_register_node, one more step to genericizing enough for upstreaming, however there are still plenty of openshift specific commands that still need to be genericized. --- roles/openshift_node/README.md | 3 +- roles/openshift_node/defaults/main.yml | 6 - .../library/openshift_register_node.py | 390 --------------------- roles/openshift_node/tasks/main.yml | 68 +--- 4 files changed, 20 insertions(+), 447 deletions(-) delete mode 100644 roles/openshift_node/library/openshift_register_node.py (limited to 'roles/openshift_node') diff --git a/roles/openshift_node/README.md b/roles/openshift_node/README.md index 9210bab16..d537a35a5 100644 --- a/roles/openshift_node/README.md +++ b/roles/openshift_node/README.md @@ -21,7 +21,6 @@ From this role: | openshift_master_public_ips | UNDEF (Required) | List of the public IPs for the openhift-master hosts | | openshift_master_ips | UNDEF (Required) | List of IP addresses for the openshift-master hosts to be used for node -> master communication | | openshift_registry_url | UNDEF (Optional) | Default docker registry to use | -| openshift_node_resources | { capacity: { cpu: , memory: } } | Resource specification for this node, cpu is the number of CPUs to advertise and memory is the amount of memory in bytes to advertise. Default values chosen when not set are the number of logical CPUs for the host and 75% of total system memory | From openshift_common: | Name | Default Value | | @@ -29,7 +28,7 @@ From openshift_common: | openshift_debug_level | 0 | Global openshift debug log verbosity | | openshift_hostname_workaround | True | | | openshift_public_ip | UNDEF (Required) | Public IP address to use for this host | -| openshift_hostname | openshift_public_ip if openshift_hostname_workaround else ansible_fqdn | hostname to use for this instance | +| openshift_hostname | UNDEF (Required) | hostname to use for this instance | Dependencies ------------ diff --git a/roles/openshift_node/defaults/main.yml b/roles/openshift_node/defaults/main.yml index e4d5ebfee..6dc73a96e 100644 --- a/roles/openshift_node/defaults/main.yml +++ b/roles/openshift_node/defaults/main.yml @@ -4,9 +4,3 @@ openshift_node_debug_level: "{{ openshift_debug_level | default(0) }}" os_firewall_allow: - service: OpenShift kubelet port: 10250/tcp -openshift_node_resources: - cpu: - memory: - cidr: -openshift_node_labels: {} -openshift_node_annotations: {} diff --git a/roles/openshift_node/library/openshift_register_node.py b/roles/openshift_node/library/openshift_register_node.py deleted file mode 100644 index 4922585d7..000000000 --- a/roles/openshift_node/library/openshift_register_node.py +++ /dev/null @@ -1,390 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# vim: expandtab:tabstop=4:shiftwidth=4 - -import os -import multiprocessing -import socket -from subprocess import check_output, Popen -from decimal import * - -DOCUMENTATION = ''' ---- -module: kubernetes_register_node -short_description: Registers a kubernetes node with a master -description: - - Registers a kubernetes node with a master -options: - name: - default: null - description: - - Identifier for this node (usually the node fqdn). - required: true - api_verison: - choices: ['v1beta1', 'v1beta3'] - default: 'v1beta1' - description: - - Kubernetes API version to use - required: true - host_ip: - default: null - description: - - IP Address to associate with the node when registering. - Available in the following API versions: v1beta1. - required: false - hostnames: - default: [] - description: - - Valid hostnames for this node. Available in the following API - versions: v1beta3. - required: false - external_ips: - default: [] - description: - - External IP Addresses for this node. Available in the following API - versions: v1beta3. - required: false - internal_ips: - default: [] - description: - - Internal IP Addresses for this node. Available in the following API - versions: v1beta3. - required: false - cpu: - default: null - description: - - Number of CPUs to allocate for this node. If not provided, then - the node will be registered to advertise the number of logical - CPUs available. When using the v1beta1 API, you must specify the - CPU count as a floating point number with no more than 3 decimal - places. API version v1beta3 and newer accepts arbitrary float - values. - required: false - memory: - default: null - description: - - Memory available for this node. If not provided, then the node - will be registered to advertise 80% of MemTotal as available - memory. When using the v1beta1 API, you must specify the memory - size in bytes. API version v1beta3 and newer accepts binary SI - and decimal SI values. - required: false -''' -EXAMPLES = ''' -# Minimal node registration -- openshift_register_node: name=ose3.node.example.com - -# Node registration using the v1beta1 API and assigning 1 CPU core and 10 GB of -# Memory -- openshift_register_node: - name: ose3.node.example.com - api_version: v1beta1 - hostIP: 192.168.1.1 - cpu: 1 - memory: 500000000 - -# Node registration using the v1beta3 API, setting an alternate hostname, -# internalIP, externalIP and assigning 3.5 CPU cores and 1 TiB of Memory -- openshift_register_node: - name: ose3.node.example.com - api_version: v1beta3 - external_ips: ['192.168.1.5'] - internal_ips: ['10.0.0.5'] - hostnames: ['ose2.node.internal.local'] - cpu: 3.5 - memory: 1Ti -''' - - -class ClientConfigException(Exception): - pass - -class ClientConfig: - def __init__(self, client_opts, module): - _, output, error = module.run_command(["/usr/bin/openshift", "ex", - "config", "view", "-o", - "json"] + client_opts, - check_rc = True) - self.config = json.loads(output) - - if not (bool(self.config['clusters']) or - bool(self.config['contexts']) or - bool(self.config['current-context']) or - bool(self.config['users'])): - raise ClientConfigException(msg="Client config missing required " \ - "values", - output=output) - - def current_context(self): - return self.config['current-context'] - - def section_has_value(self, section_name, value): - section = self.config[section_name] - if isinstance(section, dict): - return value in section - else: - val = next((item for item in section - if item['name'] == value), None) - return val is not None - - def has_context(self, context): - return self.section_has_value('contexts', context) - - def has_user(self, user): - return self.section_has_value('users', user) - - def has_cluster(self, cluster): - return self.section_has_value('clusters', cluster) - - def get_value_for_context(self, context, attribute): - contexts = self.config['contexts'] - if isinstance(contexts, dict): - return contexts[context][attribute] - else: - return next((c['context'][attribute] for c in contexts - if c['name'] == context), None) - - def get_user_for_context(self, context): - return self.get_value_for_context(context, 'user') - - def get_cluster_for_context(self, context): - return self.get_value_for_context(context, 'cluster') - -class Util: - @staticmethod - def getLogicalCores(): - return multiprocessing.cpu_count() - - @staticmethod - def getMemoryPct(pct): - with open('/proc/meminfo', 'r') as mem: - for line in mem: - entries = line.split() - if str(entries.pop(0)) == 'MemTotal:': - mem_total_kb = Decimal(entries.pop(0)) - mem_capacity_kb = mem_total_kb * Decimal(pct) - return str(mem_capacity_kb.to_integral_value() * 1024) - - return "" - - @staticmethod - def remove_empty_elements(mapping): - if isinstance(mapping, dict): - m = mapping.copy() - for key, val in mapping.iteritems(): - if not val: - del m[key] - return m - else: - return mapping - -class NodeResources: - def __init__(self, version, cpu=None, memory=None): - if version == 'v1beta1': - self.resources = dict(capacity=dict()) - self.resources['capacity']['cpu'] = cpu if cpu else Util.getLogicalCores() - self.resources['capacity']['memory'] = memory if cpu else Util.getMemoryPct(.75) - - def get_resources(self): - return Util.remove_empty_elements(self.resources) - -class NodeSpec: - def __init__(self, version, cpu=None, memory=None, cidr=None, externalID=None): - if version == 'v1beta3': - self.spec = dict(podCIDR=cidr, externalID=externalID, - capacity=dict()) - self.spec['capacity']['cpu'] = cpu if cpu else Util.getLogicalCores() - self.spec['capacity']['memory'] = memory if memory else Util.getMemoryPct(.75) - - def get_spec(self): - return Util.remove_empty_elements(self.spec) - -class NodeStatus: - def addAddresses(self, addressType, addresses): - addressList = [] - for address in addresses: - addressList.append(dict(type=addressType, address=address)) - return addressList - - def __init__(self, version, externalIPs = [], internalIPs = [], - hostnames = []): - if version == 'v1beta3': - self.status = dict(addresses = addAddresses('ExternalIP', - externalIPs) + - addAddresses('InternalIP', - internalIPs) + - addAddresses('Hostname', - hostnames)) - - def get_status(self): - return Util.remove_empty_elements(self.status) - -class Node: - def __init__(self, module, client_opts, version='v1beta1', name=None, - hostIP = None, hostnames=[], externalIPs=[], internalIPs=[], - cpu=None, memory=None, labels=dict(), annotations=dict(), - podCIDR=None, externalID=None): - self.module = module - self.client_opts = client_opts - if version == 'v1beta1': - self.node = dict(id = name, - kind = 'Node', - apiVersion = version, - hostIP = hostIP, - resources = NodeResources(version, cpu, memory), - cidr = podCIDR, - labels = labels, - annotations = annotations - ) - elif version == 'v1beta3': - metadata = dict(name = name, - labels = labels, - annotations = annotations - ) - self.node = dict(kind = 'Node', - apiVersion = version, - metadata = metadata, - spec = NodeSpec(version, cpu, memory, podCIDR, - externalID), - status = NodeStatus(version, externalIPs, - internalIPs, hostnames), - ) - - def get_name(self): - if self.node['apiVersion'] == 'v1beta1': - return self.node['id'] - elif self.node['apiVersion'] == 'v1beta3': - return self.node['name'] - - def get_node(self): - node = self.node.copy() - if self.node['apiVersion'] == 'v1beta1': - node['resources'] = self.node['resources'].get_resources() - elif self.node['apiVersion'] == 'v1beta3': - node['spec'] = self.node['spec'].get_spec() - node['status'] = self.node['status'].get_status() - return Util.remove_empty_elements(node) - - def exists(self): - _, output, error = self.module.run_command(["/usr/bin/osc", "get", - "nodes"] + self.client_opts, - check_rc = True) - if re.search(self.module.params['name'], output, re.MULTILINE): - return True - return False - - def create(self): - cmd = ['/usr/bin/osc'] + self.client_opts + ['create', 'node', '-f', '-'] - rc, output, error = self.module.run_command(cmd, - data=self.module.jsonify(self.get_node())) - if rc != 0: - if re.search("minion \"%s\" already exists" % self.get_name(), - error): - self.module.exit_json(changed=False, - msg="node definition already exists", - node=self.get_node()) - else: - self.module.fail_json(msg="Node creation failed.", rc=rc, - output=output, error=error, - node=self.get_node()) - else: - return True - -def main(): - module = AnsibleModule( - argument_spec = dict( - name = dict(required = True, type = 'str'), - host_ip = dict(type = 'str'), - hostnames = dict(type = 'list', default = []), - external_ips = dict(type = 'list', default = []), - internal_ips = dict(type = 'list', default = []), - api_version = dict(type = 'str', default = 'v1beta1', # TODO: after kube rebase, we can default to v1beta3 - choices = ['v1beta1', 'v1beta3']), - cpu = dict(type = 'str'), - memory = dict(type = 'str'), - labels = dict(type = 'dict', default = {}), # TODO: needs documented - annotations = dict(type = 'dict', default = {}), # TODO: needs documented - pod_cidr = dict(type = 'str'), # TODO: needs documented - external_id = dict(type = 'str'), # TODO: needs documented - client_config = dict(type = 'str'), # TODO: needs documented - client_cluster = dict(type = 'str', default = 'master'), # TODO: needs documented - client_context = dict(type = 'str', default = 'master'), # TODO: needs documented - client_user = dict(type = 'str', default = 'admin') # TODO: needs documented - ), - mutually_exclusive = [ - ['host_ip', 'external_ips'], - ['host_ip', 'internal_ips'], - ['host_ip', 'hostnames'], - ], - supports_check_mode=True - ) - - user_has_client_config = os.path.exists(os.path.expanduser('~/.kube/.kubeconfig')) - if not (user_has_client_config or module.params['client_config']): - module.fail_json(msg="Could not locate client configuration, " - "client_config must be specified if " - "~/.kube/.kubeconfig is not present") - - client_opts = [] - if module.params['client_config']: - client_opts.append("--kubeconfig=%s" % module.params['client_config']) - - try: - config = ClientConfig(client_opts, module) - except ClientConfigException as e: - module.fail_json(msg="Failed to get client configuration", exception=e) - - client_context = module.params['client_context'] - if config.has_context(client_context): - if client_context != config.current_context(): - client_opts.append("--context=%s" % client_context) - else: - module.fail_json(msg="Context %s not found in client config" % - client_context) - - client_user = module.params['client_user'] - if config.has_user(client_user): - if client_user != config.get_user_for_context(client_context): - client_opts.append("--user=%s" % client_user) - else: - module.fail_json(msg="User %s not found in client config" % - client_user) - - client_cluster = module.params['client_cluster'] - if config.has_cluster(client_cluster): - if client_cluster != config.get_cluster_for_context(client_cluster): - client_opts.append("--cluster=%s" % client_cluster) - else: - module.fail_json(msg="Cluster %s not found in client config" % - client_cluster) - - # TODO: provide sane defaults for some (like hostname, externalIP, - # internalIP, etc) - node = Node(module, client_opts, module.params['api_version'], - module.params['name'], module.params['host_ip'], - module.params['hostnames'], module.params['external_ips'], - module.params['internal_ips'], module.params['cpu'], - module.params['memory'], module.params['labels'], - module.params['annotations'], module.params['pod_cidr'], - module.params['external_id']) - - # TODO: attempt to support changing node settings where possible and/or - # modifying node resources - if node.exists(): - module.exit_json(changed=False, node=node.get_node()) - elif module.check_mode: - module.exit_json(changed=True, node=node.get_node()) - else: - if node.create(): - module.exit_json(changed=True, - msg="Node created successfully", - node=node.get_node()) - else: - module.fail_json(msg="Unknown error creating node", - node=node.get_node()) - - -# import module snippets -from ansible.module_utils.basic import * -if __name__ == '__main__': - main() diff --git a/roles/openshift_node/tasks/main.yml b/roles/openshift_node/tasks/main.yml index e380ba1fb..c039e3f05 100644 --- a/roles/openshift_node/tasks/main.yml +++ b/roles/openshift_node/tasks/main.yml @@ -1,27 +1,29 @@ --- +- name: Test if node certs and config exist + stat: path={{ item }} + failed_when: not result.stat.exists + register: result + with_items: + - "{{ cert_path }}" + - "{{ cert_path }}/cert.crt" + - "{{ cert_path }}/key.key" + - "{{ cert_path }}/.kubeconfig" + - "{{ cert_path }}/server.crt" + - "{{ cert_path }}/server.key" + - "{{ cert_parent_path }}/ca/cert.crt" + #- "{{ cert_path }}/node.yaml" + - name: Install OpenShift Node package yum: pkg=openshift-node state=installed -- local_action: command /usr/bin/mktemp -d /tmp/openshift-ansible-XXXXXXX - register: mktemp - -- name: Retrieve OpenShift Master credentials - local_action: command /usr/bin/rsync --compress --archive --rsh 'ssh -S none -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' root@{{ openshift_master_public_ips[0] }}:/var/lib/openshift/openshift.local.certificates/admin/ {{ mktemp.stdout }} - ignore_errors: yes - -- file: path=/var/lib/openshift/openshift.local.certificates/admin state=directory - -- name: Store OpenShift Master credentials - local_action: command /usr/bin/rsync --compress --archive --rsh 'ssh -S none -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' {{ mktemp.stdout }}/ root@{{ openshift_public_ip }}:/var/lib/openshift/openshift.local.certificates/admin - ignore_errors: yes - -- local_action: file name={{ mktemp.stdout }} state=absent - +# --create-certs=false is a temporary workaround until +# https://github.com/openshift/origin/pull/1361 is merged upstream and it is +# the default for nodes - name: Configure OpenShift Node settings lineinfile: dest: /etc/sysconfig/openshift-node regexp: '^OPTIONS=' - line: "OPTIONS=\"--master=https://{{ openshift_master_ips[0] }}:8443 --hostname={{ openshift_hostname }} --loglevel={{ openshift_node_debug_level }}\"" + line: "OPTIONS=\"--hostname={{ openshift_hostname }} --loglevel={{ openshift_node_debug_level }} --create-certs=false\"" notify: - restart openshift-node @@ -47,42 +49,10 @@ option: externally_managed value: "{{ openshift_node_manage_service_externally }}" -# fixme: Once the openshift_cluster playbook is published state should be started -# Always bounce service to pick up new credentials - name: Start and enable openshift-node - service: name=openshift-node enabled=yes state=restarted + service: name=openshift-node enabled=yes state=started when: not openshift_node_manage_service_externally - name: Disable openshift-node if openshift-node is managed externally service: name=openshift-node enabled=false when: openshift_node_manage_service_externally - -# TODO: create an os_vars role that has generic env related config and move -# the root kubeconfig setting there, cannot use dependencies to force ordering -# with openshift_node and openshift_master because the way conditional -# dependencies work with current ansible would also exclude the -# openshift_common dependency. -- name: Create .kube directory - file: - path: /root/.kube - state: directory - mode: 0700 -- name: Configure root user kubeconfig - command: cp /var/lib/openshift/openshift.local.certificates/admin/.kubeconfig /root/.kube/.kubeconfig - args: - creates: /root/.kube/.kubeconfig - -- name: Register node (if not already registered) - openshift_register_node: - name: "{{ openshift_hostname }}" - api_version: v1beta1 - cpu: "{{ openshift_node_resources.cpu }}" - memory: "{{ openshift_node_resources.memory }}" - pod_cidr: "{{ openshift_node_resources.cidr }}" - host_ip: "{{ ansible_default_ipv4.address }}" - labels: "{{ openshift_node_labels }}" - annotations: "{{ openshift_node_annotations }}" - # TODO: support customizing other attributes such as: client_config, - # client_cluster, client_context, client_user - # TODO: updated for v1beta3 changes after rebase: hostnames, external_ips, - # internal_ips, external_id -- cgit v1.2.3