diff --git a/skydive/README.md b/skydive/README.md index e7ebd45f..646b1925 100644 --- a/skydive/README.md +++ b/skydive/README.md @@ -134,7 +134,7 @@ openstack-ansible -i /opt/openstack-ansible/inventory/dynamic_inventory.py \ More on using overlay inventories can be seen in the `overlay-inventory` directory. -##### Configuration | Haproxy +##### Configuration | Haproxy (frontend) The example overlay inventory contains a section for general haproxy configuration which exposes the skydive UI internally. @@ -170,12 +170,27 @@ This config will provide access to the web UI for both **skydive** and * **Skydive** runs on port `8082` * **Traefik** runs on port `8090` +##### OpenStack Integration + +Skydive can be configured to work with OpenStack. For this to work a +`clouds.yaml` must be present on one of the nodes used within the deployment; +the path is typically to the clouds config is typically +`$HOME/.config/openstack/clouds.yaml`. The playbooks will use the +`clouds.yaml` file to read nessisary credentials used to create a new users +and roles to be used with `skydive` and to enable neutron probes within the +`skydive` agent. + +When OpenStack integration is enabled, all authentication will be done through +keystone. User access to the skydive UI will be restricted to only users that +have the skydive role assigned to them. + +All available options for the OpenStack integration can be found in the +`defaults/main.yml` file. + ### Validating the skydive installation Post-deployment, the skydive installation can be validated by simply running the `validateSkydive.yml` playbook. TODOs: -[] Setup cert based agent/server auth -[] Add OpenStack integration -[] Document OpenStack integration, what it adds to the admin service +- [] Setup cert based agent/server auth diff --git a/skydive/installSkydive.yml b/skydive/installSkydive.yml index b5562c91..d9f86a1f 100644 --- a/skydive/installSkydive.yml +++ b/skydive/installSkydive.yml @@ -37,6 +37,7 @@ patterns: "*skydive*" register: files_to_copy delegate_to: "{{ skydive_staging_node }}" + run_once: true become: false - name: Install built skydive copy: @@ -55,6 +56,7 @@ dest: "/tmp/skydive/{{ ansible_architecture }}/{{ skydive_binary_url | basename }}" mode: '0755' delegate_to: "{{ skydive_staging_node }}" + run_once: true become: false - name: Install binary skydive copy: @@ -91,6 +93,7 @@ patterns: "*traefik*" register: files_to_copy delegate_to: "{{ traefik_staging_node }}" + run_once: true become: false - name: Install built traefik copy: @@ -109,6 +112,7 @@ dest: "/tmp/traefik/{{ ansible_architecture }}/{{ traefik_binary_url | basename }}" mode: '0755' delegate_to: "{{ traefik_staging_node }}" + run_once: true become: false - name: Install binary traefik copy: diff --git a/skydive/roles/skydive_common/defaults/main.yml b/skydive/roles/skydive_common/defaults/main.yml index 86304702..06270faa 100644 --- a/skydive/roles/skydive_common/defaults/main.yml +++ b/skydive/roles/skydive_common/defaults/main.yml @@ -108,32 +108,23 @@ skydive_basic_auth_file: /var/lib/skydive/skydive.secret skydive_basic_auth_users: {} # Skydive openstack setup -skydive_os_service_username: "{{ skydive_username }}.service" -skydive_os_service_password: "{{ skydive_password }}" -skydive_os_service_tenant_name: service -skydive_os_service_domain_name: Default -skydive_os_service_region_name: RegionOne -skydive_os_service_endpoint_type: internal -skydive_os_service_insecure: true -skydive_os_auth_url: null -skydive_auth_os_tenant_name: "{{ skydive_username }}" -skydive_auth_os_domain_name: Default -skydive_auth_os_domain_id: default -skydive_auth_os_user_role: admin +## These options are normally undefined, if undefined the value will be pulled from the local clouds.yml. +skydive_openstack_enabled: false +# skydive_os_auth_url: http://localhost:5000/v3 +# skydive_os_region_name: RegionOne +# skydive_os_endpoint_type: public - -os_auth_url: -os_username: -os_password: -os_tenant_name: admin -os_user_domain_name: Default -os_project_domain_name: Default -os_identity_api_version: 3 - -# Role of the user created that will be used for the probe -# authentication +skydive_os_cloud: default +skydive_os_cloud_file: "{{ ansible_env.HOME }}/.config/openstack/clouds.yaml" +skydive_os_domain_name: Default +skydive_os_project_name: "{{ skydive_username }}" +skydive_os_user_name: "{{ skydive_username }}" +skydive_os_user_role: admin +skydive_os_service_user: "{{ skydive_username }}.service" skydive_os_service_user_role: admin +skydive_os_service_password: "{{ skydive_password }}" +skydive_os_service_insecure: true # Configuration overrides can be set using a config template. diff --git a/skydive/roles/skydive_common/tasks/main.yml b/skydive/roles/skydive_common/tasks/main.yml index 9da66410..a998b36f 100644 --- a/skydive/roles/skydive_common/tasks/main.yml +++ b/skydive/roles/skydive_common/tasks/main.yml @@ -68,4 +68,31 @@ tags: - package_install +- name: Check for openstack deployment + block: + - name: Slurp clouds file + slurp: + src: "{{ skydive_os_cloud_file }}" + register: clouds_file + + - name: Enable OpenStack integration + set_fact: + clouds_yaml: "{{ clouds_file['content'] | b64decode | from_yaml }}" + skydive_auth_type: mykeystone + skydive_openstack_enabled: true + run_once: true + delegate_to: "{{ item }}" + delegate_facts: true + with_items: "{{ ansible_play_hosts }}" + + - include_tasks: skydive_keystone.yml + run_once: true + rescue: + - name: Notice + debug: + msg: >- + OpenStack setup is not possible, running in without it. + when: + - not (skydive_openstack_enabled | bool) + - include_tasks: skydive_setup.yml diff --git a/skydive/roles/skydive_common/tasks/skydive_keystone.yml b/skydive/roles/skydive_common/tasks/skydive_keystone.yml new file mode 100644 index 00000000..f4189de0 --- /dev/null +++ b/skydive/roles/skydive_common/tasks/skydive_keystone.yml @@ -0,0 +1,131 @@ +--- +# Copyright 2019, Rackspace US, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Check credentials and clouds data + fail: + msg: >- + The following variable "{{ item.default }}" is undefined and no known value was defined + in the "{{ skydive_os_cloud_file }}" file. + when: + - hostvars[inventory_hostname][item.default] is undefined + - clouds_yaml['clouds'] is undefined + - clouds_yaml['clouds'][skydive_os_cloud] is undefined + - clouds_yaml['clouds'][skydive_os_cloud]['auth'] is undefined + - clouds_yaml['clouds'][skydive_os_cloud]['auth'][item.cfg] is undefined + with_items: + - default: "skydive_os_auth_url" + cfg: "auth_url" + +- name: Create skydive venv + command: "/usr/bin/virtualenv --no-site-packages --no-setuptools /opt/skydive" + args: + creates: /opt/skydive/bin/pip + +- name: Setup skydive venv + pip: + name: + - pip + - setuptools + extra_args: "-U" + virtualenv: /opt/skydive + +- name: Ensure the openstacksdk is installed + pip: + name: + - openstacksdk + extra_args: "-U" + virtualenv: /opt/skydive + +- name: Capture current ansible python interpreter + set_fact: + old_ansible_python_interpreter: "{{ ansible_python_interpreter | default('/usr/bin/python') }}" + +- name: Set ansible python interpreter to skydive venv + set_fact: + ansible_python_interpreter: "/opt/skydive/bin/python" + +- name: Add skydive project + os_project: + cloud: "{{ skydive_os_cloud }}" + state: present + name: "{{ skydive_os_project_name }}" + description: "Skydive admin project" + domain_id: "{{ skydive_os_domain_name }}" + verify: "{{ not (skydive_os_service_insecure | bool) }}" + enabled: true + register: keystone_api + until: keystone_api is success + retries: 5 + delay: 10 + +- name: Add skydive user + os_user: + cloud: "{{ skydive_os_cloud }}" + state: present + name: "{{ skydive_os_user_name }}" + password: "{{ skydive_password }}" + update_password: on_create + domain: "{{ skydive_os_domain_name }}" + default_project: "{{ skydive_os_project_name }}" + verify: "{{ not (skydive_os_service_insecure | bool) }}" + enabled: true + register: keystone_api + until: keystone_api is success + retries: 5 + delay: 10 + +- name: Assign skydive user role + os_user_role: + cloud: "{{ skydive_os_cloud }}" + state: present + user: "{{ skydive_os_user_name }}" + role: "{{ skydive_os_user_role }}" + project: "{{ skydive_os_project_name }}" + verify: "{{ not (skydive_os_service_insecure | bool) }}" + register: keystone_api + until: keystone_api is success + retries: 5 + delay: 10 + +- name: Add skydive service user + os_user: + cloud: "{{ skydive_os_cloud }}" + state: present + name: "{{ skydive_os_service_user }}" + password: "{{ skydive_os_service_password }}" + domain: "{{ skydive_os_domain_name }}" + default_project: "{{ skydive_os_project_name }}" + verify: "{{ not (skydive_os_service_insecure | bool) }}" + register: keystone_api + until: keystone_api is success + retries: 5 + delay: 10 + +- name: Assign skydive service user role + os_user_role: + cloud: "{{ skydive_os_cloud }}" + state: present + user: "{{ skydive_os_service_user }}" + role: "{{ skydive_os_service_user_role }}" + project: "{{ skydive_os_project_name }}" + verify: "{{ not (skydive_os_service_insecure | bool) }}" + register: keystone_api + until: keystone_api is success + retries: 5 + delay: 10 + +- name: Reset ansible python + set_fact: + ansible_python_interpreter: "{{ old_ansible_python_interpreter }}" diff --git a/skydive/roles/skydive_common/templates/skydive.yml.j2 b/skydive/roles/skydive_common/templates/skydive.yml.j2 index a7a89cc5..b0b28eff 100644 --- a/skydive/roles/skydive_common/templates/skydive.yml.j2 +++ b/skydive/roles/skydive_common/templates/skydive.yml.j2 @@ -43,7 +43,7 @@ http: # queue_size: 10000 # enable write compression - # enable_write_compression: true + enable_write_compression: true {% if inventory_hostname in groups['skydive_analyzers'] %} analyzer: @@ -191,47 +191,50 @@ agent: # Probes used to capture topology information like interfaces, # bridges, namespaces, etc... # Available: ovsdb, docker, neutron, opencontrail, socketinfo, lxd, lldp -{% if skydive_docker_exists | bool %} -{% set _ = skydive_probes.append('docker') %} -{% endif %} -{% if skydive_ovs_db_exists | bool %} -{% set _ = skydive_probes.append('ovsdb') %} -{% endif %} +{% if skydive_docker_exists | bool %} +{% set _ = skydive_probes.append('docker') %} +{% endif %} +{% if skydive_ovs_db_exists | bool %} +{% set _ = skydive_probes.append('ovsdb') %} +{% endif %} +{% if skydive_openstack_enabled | bool %} +{% set _ = skydive_probes.append('neutron') %} +{% endif %} probes: {{ skydive_probes | to_json }} netlink: # delay in seconds between two metric updates - # metrics_update: 30 + metrics_update: 30 +{% if skydive_openstack_enabled | bool %} # Define OpenStack Neutron credentials and the enpoint type # used by the neutron probe neutron: - # auth_url: - # username: neutron - # password: secret - # tenant_name: service - # region_name: RegionOne - # domain_name: Default - # ssl_insecure: false - - # The endpoint_type value must be 'public', 'internal' or 'admin' - # endpoint_type: public + auth_url: {{ skydive_os_auth_url | default(clouds_yaml['clouds'][skydive_os_cloud]['auth']['auth_url']) }} + username: {{ skydive_os_service_user }} + password: {{ skydive_os_service_password }} + tenant_name: {{ skydive_os_project_name }} + region_name: {{ skydive_os_region_name | default(clouds_yaml['clouds'][skydive_os_cloud]['region_name']) }} + domain_name: {{ skydive_os_domain_name }} + ssl_insecure: {{ not (skydive_os_service_insecure | bool) }} + endpoint_type: {{ skydive_os_endpoint_type | default(clouds_yaml['clouds'][skydive_os_cloud]['interface']) }} +{% endif %} lldp: # Interfaces to listen for LLDP frames. If no list is specified, # use all interfaces interfaces: -{% if skydive_libvirt_exists | bool %} +{% if skydive_libvirt_exists | bool %} libvirt: url: qemu:///system -{% endif %} +{% endif %} -{% if skydive_runc_exists | bool %} +{% if skydive_runc_exists | bool %} runc: run_path: - /var/run/runc -{% endif %} +{% endif %} capture: # Period in second to get capture stats from the probe. Note this @@ -239,7 +242,6 @@ agent: metadata: # info: This is compute node -{% endif %} dpdk: # DPDK port listening flows from @@ -253,6 +255,7 @@ dpdk: # debug message every n seconds # debug: 1 +{% if skydive_ovs_db_exists | bool %} sflow: # Default listening address is 127.0.0.1 # bind_address: 127.0.0.1 @@ -262,7 +265,6 @@ sflow: # port_min: 6345 # port_max: 6355 -{% if skydive_ovs_db_exists | bool %} ovs: # ovsdb connection, Format supported : # * addr:port @@ -299,12 +301,12 @@ ovs: address: # Map translating bridge names into URL for remote connection # - bridge: ssl:xxx.yyy.zzz.ttt:port -{% endif %} +{% endif %} -{% if skydive_docker_exists | bool %} +{% if skydive_docker_exists | bool %} docker: url: unix://{{ skydive_docker_socket }} -{% endif %} +{% endif %} netns: # allow to specify where the netns probe is watching network namespace @@ -319,7 +321,9 @@ opencontrail: # UDP dest port for MPLS traffic # mpls_udp_port: 51234 +{% endif %} +{% if inventory_hostname in groups['skydive_analyzers'] %} storage: # Elasticsearch backend information. myelasticsearch: @@ -351,6 +355,7 @@ storage: # Memory backend mymemory: # driver: memory +{% endif %} logging: # level: INFO @@ -383,19 +388,22 @@ auth: # user1: secret1 # user2: secret2 +{% if skydive_openstack_enabled | bool %} mykeystone: # Define a basic auth authentication backend type: keystone - auth_url: {{ skydive_os_auth_url }} + auth_url: {{ skydive_os_auth_url | default(clouds_yaml['clouds'][skydive_os_cloud]['auth']['auth_url']) }} # define the tenant and the domain that the users have to belong to - tenant_name: {{ skydive_auth_os_tenant_name }} - domain_name: {{ skydive_auth_os_domain_name }} + tenant_name: {{ skydive_os_project_name }} + domain_name: {{ skydive_os_domain_name }} # define which role an authenticated user will have. Only used for API authentication. # two roles are predefined, admin and guest. - role: {{ skydive_auth_os_user_role }} + role: {{ skydive_os_user_role }} +{% endif %} +{% if inventory_hostname in groups['skydive_analyzers'] %} etcd: # server parameters # when 'embedded' is set to true, the analyzer will start an embedded etcd server @@ -410,25 +418,27 @@ etcd: # data_dir: /var/lib/skydive/etcd # client parameters -{% if skydive_etcd_servers %} +{% if skydive_etcd_servers %} servers: {{ skydive_etcd_servers | to_json }} -{% endif %} +{% endif %} # name to use for clustering, by default it is set to the host id name: {{ inventory_hostname }} # list of peers for etcd clustering between analyzers # each entry is composed of the peer name and the endpoints for this peer -{% set peers = {} %} -{% for node in groups['skydive_analyzers'] %} -{% if node in ansible_play_hosts %} -{% set _ansible_interface_name = hostvars[node]['skydive_network_device'] | default(hostvars[node]['ansible_default_ipv4']['interface']) | replace('-', '_') %} -{% set _ = peers.__setitem__(inventory_hostname, 'http://' ~ (hostvars[node]['skydive_bind_address'] | default(hostvars[node]["ansible_" ~ _ansible_interface_name]['ipv4']['address'])) ~ ':' ~ skydive_etcd_port) %} -{% endif %} -{% endfor %} +{% set peers = {} %} +{% for node in groups['skydive_analyzers'] %} +{% if node in ansible_play_hosts %} +{% set _ansible_interface_name = hostvars[node]['skydive_network_device'] | default(hostvars[node]['ansible_default_ipv4']['interface']) | replace('-', '_') %} +{% set _ = peers.__setitem__(inventory_hostname, 'http://' ~ (hostvars[node]['skydive_bind_address'] | default(hostvars[node]["ansible_" ~ _ansible_interface_name]['ipv4']['address'])) ~ ':' ~ skydive_etcd_port) %} +{% endif %} +{% endfor %} peers: {{ skydive_etcd_peers | default(peers) | to_json }} # client_timeout: 5 +{% endif %} +{% if inventory_hostname in groups['skydive_agents'] %} flow: # Without any new packets, a flow expires after flow.expire # seconds @@ -455,6 +465,7 @@ flow: # 1194: OPENVPN udp: # 1194: OPENVPN +{% endif %} ui: # Specify the extra assets folder. Javascript and CSS files present in this