From 6292de70ae19c84c01d12562c0e6682918fe30f8 Mon Sep 17 00:00:00 2001 From: Jack Lucas Date: Mon, 10 Sep 2018 12:14:29 +0000 Subject: Add support for TLS init container Change-Id: I118af2c8a0294ffc89e045f8cdae24dfb7e57ab6 Issue-ID: DCAEGEN2-591 Signed-off-by: Jack Lucas --- k8s/README.md | 27 +++++++++++++++++---------- k8s/configure/configure.py | 7 +++++++ k8s/k8s-node-type.yaml | 24 +++++++++++++++++++++++- k8s/k8sclient/k8sclient.py | 38 +++++++++++++++++++++++++++++++------- k8s/k8splugin/tasks.py | 10 ++++++++++ k8s/pom.xml | 2 +- k8s/setup.py | 2 +- 7 files changed, 90 insertions(+), 20 deletions(-) diff --git a/k8s/README.md b/k8s/README.md index 5b2d0da..2f4b3a6 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -16,6 +16,9 @@ creates the following Kubernetes entities: - If the blueprint specifies a logging directory via the `log_info` property, the `Deployment` includes a second container, running the `filebeat` logging sidecar that ships logging information to the ONAP ELK stack. The `Deployment` will include some additional volumes needed by filebeat. + - If the blueprint specifies that the component uses TLS (HTTPS) via the `tls_info` property, the `Deployment` includes an init container, + a volume that holds TLS certificate artifacts, and volume mounts on the init container and the component's container. The init container + populates the TLS certificate artifacts volume with certificates, keys, keystores, etc. - If the blueprint indicates that the component exposes any ports, the plugin will create a Kubernetes `Service` that allocates an address in the Kubernetes network address space that will route traffic to a container that's running the component. This `Service` provides a fixed "virtual IP" for the component. @@ -40,16 +43,20 @@ address=10.12.5.115:30270 Additional configuration information is stored in the Consul KV store under the key `k8s-plugin`. The configuration is provided as JSON object with the following properties: - - namespace: k8s namespace to use for DCAE - - consul_dns_name: k8s internal DNS name for Consul (passed to containers) - - image_pull_secrets: list of names of k8s secrets for accessing Docker registries, with the following properties: - - filebeat: object containing onfiguration for setting up filebeat container - - log_path: mount point for log volume in filebeat container - - data_path: mount point for data volume in filebeat container - - config_path: mount point for config volume in filebeat container - - config_subpath: subpath for config data in filebeat container - - config_map: name of a ConfigMap holding the filebeat configuration file - - image: Docker image to use for filebeat + - `namespace`: k8s namespace to use for DCAE + - `consul_dns_name`: k8s internal DNS name for Consul (passed to containers) + - `image_pull_secrets`: list of names of k8s secrets for accessing Docker registries, with the following properties: + - `filebeat`: object containing onfiguration for setting up filebeat container + - `log_path`: mount point for log volume in filebeat container + - `data_path`: mount point for data volume in filebeat container + - `config_path`: mount point for config volume in filebeat container + - `config_subpath`: subpath for config data in filebeat container + - `config_map`: name of a ConfigMap holding the filebeat configuration file + - `image`: Docker image to use for filebeat + - `tls`: object containing configuration for setting up TLS init container + - `cert_path`: mount point for the TLS certificate artifact volume in the init container + - `image`: Docker image to use for the TLS init container + #### Kubernetes access information The plugin accesses a Kubernetes cluster. The information and credentials for accessing a cluster are stored in a "kubeconfig" diff --git a/k8s/configure/configure.py b/k8s/configure/configure.py index fcf4044..03077d2 100644 --- a/k8s/configure/configure.py +++ b/k8s/configure/configure.py @@ -32,6 +32,9 @@ FB_CONFIG_SUBPATH = "filebeat.yml" FB_CONFIG_MAP = "filebeat-conf" FB_IMAGE = "docker.elastic.co/beats/filebeat:5.5.0" +TLS_CERT_PATH = "/opt/tls/shared" +TLS_IMAGE = "tls-init:latest" + def _set_defaults(): """ Set default configuration parameters """ return { @@ -45,6 +48,10 @@ def _set_defaults(): "config_subpath" : FB_CONFIG_SUBPATH, # subpath for config data in filebeat container "config_map" : FB_CONFIG_MAP, # ConfigMap holding the filebeat configuration "image": FB_IMAGE # Docker image to use for filebeat + }, + "tls": { # Configuration for setting up TLS init container + "cert_path" : TLS_CERT_PATH, # mount point for certificate volume in TLS init container + "image": TLS_IMAGE # Docker image to use for TLS init container } } diff --git a/k8s/k8s-node-type.yaml b/k8s/k8s-node-type.yaml index d43d76a..5dae20a 100644 --- a/k8s/k8s-node-type.yaml +++ b/k8s/k8s-node-type.yaml @@ -25,7 +25,7 @@ plugins: k8s: executor: 'central_deployment_agent' package_name: k8splugin - package_version: 1.4.2 + package_version: 1.4.3 data_types: @@ -79,6 +79,22 @@ data_types: type: string required: false + dcae.types.TLSInfo: + description: > + Information for using TLS (HTTPS). (The properties all have to be declared as not + required, otherwise the tls_info property on the node would also be required.) + properties: + cert_directory: + description: > + The path in the container where the component expects to find TLS-related data. + type: string + required: false + use_tls: + description: > + Flag indicating whether TLS (HTTPS) is to be used + type: boolean + required: false + node_types: dcae.nodes.ContainerizedComponent: # Bese type for all containerized components @@ -109,6 +125,12 @@ node_types: Information for setting up centralized logging via ELK. required: false + tls_info: + type: dcae.types.TLSInfo + description: > + Information for setting up TLS (HTTPS). + required: false + replicas: type: integer description: > diff --git a/k8s/k8sclient/k8sclient.py b/k8s/k8sclient/k8sclient.py index 1c30534..eff51a3 100644 --- a/k8s/k8sclient/k8sclient.py +++ b/k8s/k8sclient/k8sclient.py @@ -84,7 +84,7 @@ def _parse_interval(t): raise ValueError("Bad interval specification: {0}".format(t)) return time -def _create_probe(hc, port): +def _create_probe(hc, port, use_tls=False): ''' Create a Kubernetes probe based on info in the health check dictionary hc ''' probe_type = hc['type'] probe = None @@ -99,7 +99,7 @@ def _create_probe(hc, port): http_get = client.V1HTTPGetAction( path = hc['endpoint'], port = port, - scheme = probe_type.upper() + scheme = 'HTTPS' if use_tls else probe_type.upper() ) ) elif probe_type in ['script', 'docker']: @@ -114,7 +114,7 @@ def _create_probe(hc, port): ) return probe -def _create_container_object(name, image, always_pull, env={}, container_ports=[], volume_mounts = [], readiness = None): +def _create_container_object(name, image, always_pull, use_tls=False, env={}, container_ports=[], volume_mounts = [], readiness = None): # Set up environment variables # Copy any passed in environment variables env_vars = [client.V1EnvVar(name=k, value=env[k]) for k in env.keys()] @@ -130,7 +130,7 @@ def _create_container_object(name, image, always_pull, env={}, container_ports=[ hc_port = None if len(container_ports) > 0: hc_port = container_ports[0] - probe = _create_probe(readiness, hc_port) + probe = _create_probe(readiness, hc_port, use_tls) # Define container for pod return client.V1Container( @@ -145,6 +145,7 @@ def _create_container_object(name, image, always_pull, env={}, container_ports=[ def _create_deployment_object(component_name, containers, + init_containers, replicas, volumes, labels={}, @@ -166,6 +167,7 @@ def _create_deployment_object(component_name, metadata=client.V1ObjectMeta(labels=labels), spec=client.V1PodSpec(hostname=component_name, containers=containers, + init_containers=init_containers, volumes=volumes, image_pull_secrets=ips) ) @@ -333,6 +335,9 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, * "config_subpath" : subpath for config data in filebeat container "config_map" : ConfigMap holding the filebeat configuration "image": Docker image to use for filebeat + - tls: a dictionary of TLS init container parameters: + "cert_path": mount point for certificate volume in init container + "image": Docker image to use for TLS init container kwargs may have: - volumes: array of volume objects, where a volume object is: {"host":{"path": "/path/on/host"}, "container":{"bind":"/path/on/container","mode":"rw_or_ro"} @@ -341,6 +346,8 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, * - msb_list: array of msb objects, where an msb object is as described in msb/msb.py. - log_info: an object with info for setting up ELK logging, with the form: {"log_directory": "/path/to/container/log/directory", "alternate_fb_path" : "/alternate/sidecar/log/path"} + - tls_info: an object with info for setting up TLS (HTTPS), with the form: + {"use_tls": true, "cert_directory": "/path/to/container/cert/directory" } - labels: dict with label-name/label-value pairs, e.g. {"cfydeployment" : "lsdfkladflksdfsjkl", "cfynode":"mycomponent"} These label will be set on all the pods deployed as a result of this deploy() invocation. - readiness: dict with health check info; if present, used to create a readiness probe for the main container. Includes: @@ -375,6 +382,7 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, * # Initialize the list of containers that will be part of the pod containers = [] + init_containers = [] # Set up the ELK logging sidecar container, if needed log_info = kwargs.get("log_info") @@ -395,7 +403,7 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, * sidecar_volume_mounts.append(client.V1VolumeMount(name="filebeat-data", mount_path=fb["data_path"])) # Create the container for the sidecar - containers.append(_create_container_object("filebeat", fb["image"], False, {}, [], sidecar_volume_mounts)) + containers.append(_create_container_object("filebeat", fb["image"], False, False, {}, [], sidecar_volume_mounts)) # Create the volume for the sidecar configuration data and the volume mount for it # The configuration data is in a k8s ConfigMap that should be created when DCAE is installed. @@ -404,14 +412,30 @@ def deploy(namespace, component_name, image, replicas, always_pull, k8sconfig, * sidecar_volume_mounts.append( client.V1VolumeMount(name="filebeat-conf", mount_path=fb["config_path"], sub_path=fb["config_subpath"])) + # Set up the TLS init container, if needed + tls_info = kwargs.get("tls_info") + use_tls = False + if tls_info and "use_tls" in tls_info and tls_info["use_tls"]: + if "cert_directory" in tls_info and len(tls_info["cert_directory"]) > 0: + use_tls = True + tls_config = k8sconfig["tls"] + + # Create the certificate volume and volume mounts + volumes.append(client.V1Volume(name="tls-info", empty_dir=client.V1EmptyDirVolumeSource())) + volume_mounts.append(client.V1VolumeMount(name="tls-info", mount_path=tls_info["cert_directory"])) + init_volume_mounts = [client.V1VolumeMount(name="tls-info", mount_path=tls_config["cert_path"])] + + # Create the init container + init_containers.append(_create_container_object("init-tls", tls_config["image"], False, False, {}, [], init_volume_mounts)) + # Create the container for the component # Make it the first container in the pod - containers.insert(0, _create_container_object(component_name, image, always_pull, kwargs.get("env", {}), container_ports, volume_mounts, kwargs["readiness"])) + containers.insert(0, _create_container_object(component_name, image, always_pull, use_tls, kwargs.get("env", {}), container_ports, volume_mounts, kwargs["readiness"])) # Build the k8s Deployment object labels = kwargs.get("labels", {}) labels.update({"app": component_name}) - dep = _create_deployment_object(component_name, containers, replicas, volumes, labels, pull_secrets=k8sconfig["image_pull_secrets"]) + dep = _create_deployment_object(component_name, containers, init_containers, replicas, volumes, labels, pull_secrets=k8sconfig["image_pull_secrets"]) # Have k8s deploy it ext.create_namespaced_deployment(namespace, dep) diff --git a/k8s/k8splugin/tasks.py b/k8s/k8splugin/tasks.py index d32ce30..ba71bd9 100644 --- a/k8s/k8splugin/tasks.py +++ b/k8s/k8splugin/tasks.py @@ -296,6 +296,7 @@ def _create_and_start_container(container_name, image, **kwargs): volumes=kwargs.get("volumes",[]), ports=kwargs.get("ports",[]), msb_list=kwargs.get("msb_list"), + tls_info=kwargs.get("tls_info"), env = env, labels = kwargs.get("labels", {}), log_info=kwargs.get("log_info"), @@ -324,6 +325,10 @@ def _parse_cloudify_context(**kwargs): if "log_info" in ctx.node.properties and "log_directory" in ctx.node.properties["log_info"]: kwargs["log_info"] = ctx.node.properties["log_info"] + # Pick up TLS info if present + if "tls_info" in ctx.node.properties: + kwargs["tls_info"] = ctx.node.properties["tls_info"] + # Pick up replica count and always_pull_image flag if "replicas" in ctx.node.properties: kwargs["replicas"] = ctx.node.properties["replicas"] @@ -380,6 +385,7 @@ def _create_and_start_component(**kwargs): "ports": kwargs.get("ports", None), "envs": kwargs.get("envs", {}), "log_info": kwargs.get("log_info", {}), + "tls_info": kwargs.get("tls_info", {}), "labels": kwargs.get("labels", {}), "readiness": kwargs.get("readiness",{})} _create_and_start_container(service_component_name, image, **sub_kwargs) @@ -524,6 +530,10 @@ def create_and_start_container_for_platforms(**kwargs): if "log_info" in ctx.node.properties and "log_directory" in ctx.node.properties["log_info"]: kwargs["log_info"] = ctx.node.properties["log_info"] + # Pick up TLS info if present + if "tls_info" in ctx.node.properties: + kwargs["tls_info"] = ctx.node.properties["tls_info"] + # Pick up replica count and always_pull_image flag if "replicas" in ctx.node.properties: kwargs["replicas"] = ctx.node.properties["replicas"] diff --git a/k8s/pom.xml b/k8s/pom.xml index 14baaf7..ea34f78 100644 --- a/k8s/pom.xml +++ b/k8s/pom.xml @@ -28,7 +28,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property. org.onap.dcaegen2.platform.plugins k8s k8s-plugin - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT http://maven.apache.org UTF-8 diff --git a/k8s/setup.py b/k8s/setup.py index 0fa7634..5897f61 100644 --- a/k8s/setup.py +++ b/k8s/setup.py @@ -23,7 +23,7 @@ from setuptools import setup setup( name='k8splugin', description='Cloudify plugin for containerized components deployed using Kubernetes', - version="1.4.2", + version="1.4.3", author='J. F. Lucas, Michael Hwang, Tommy Carpenter', packages=['k8splugin','k8sclient','msb','configure'], zip_safe=False, -- cgit 1.2.3-korg