From 320a0e9b6d3dadba4063e52076e732ddb0ad06ba Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 15:36:03 -0800 Subject: [PATCH 01/33] Adds Wordpress example --- examples/wordpress/README.md | 157 ++++++++++++++ examples/wordpress/images/nginx/Dockerfile | 2 + examples/wordpress/images/nginx/Makefile | 18 ++ examples/wordpress/images/nginx/default.conf | 48 +++++ examples/wordpress/wordpress-resources.yaml | 11 + examples/wordpress/wordpress.jinja | 80 ++++++++ examples/wordpress/wordpress.jinja.schema | 69 +++++++ .../replicatedservice/v2/replicatedservice.py | 194 ++++++++++++++++++ .../v2/replicatedservice.py.schema | 91 ++++++++ 9 files changed, 670 insertions(+) create mode 100644 examples/wordpress/README.md create mode 100644 examples/wordpress/images/nginx/Dockerfile create mode 100644 examples/wordpress/images/nginx/Makefile create mode 100644 examples/wordpress/images/nginx/default.conf create mode 100644 examples/wordpress/wordpress-resources.yaml create mode 100644 examples/wordpress/wordpress.jinja create mode 100644 examples/wordpress/wordpress.jinja.schema create mode 100644 types/replicatedservice/v2/replicatedservice.py create mode 100644 types/replicatedservice/v2/replicatedservice.py.schema diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md new file mode 100644 index 000000000..14cbc2ae9 --- /dev/null +++ b/examples/wordpress/README.md @@ -0,0 +1,157 @@ +# Wordpress Example + +Welcome to the Wordpress example. It shows you how to deploy a Wordpress application using Deployment Manager. + +## Prerequisites + + +### Deployment Manager +First, make sure DM is installed in your Kubernetes cluster by following the instructions in the top level +[README.md](../../README.md). + +### Google Cloud Resources +The Wordpress application will make use of several persistent disks, which we will host on Google Cloud. To create these disks we will create a deployment using Google Cloud Deployment Manager: +```gcloud deployment-manager deployments create wordpress-resources --config wordpress-resources.yaml``` + +where `wordpress-resources.yaml` looks as follows: + +``` +resources: +- name: nfs-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +- name: mysql-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +``` + +### Privileged containers +To use NFS we need to be able to launch privileged containers. If your Kubernetes cluster doesn't support this you can either manually change this in every Kubernetes minion or you could set the flag at `kubernetes/saltbase/pillar/privilege.sls` to true and (re)launch your Kubernetes cluster. Once Kubernetes 1.1 releases this should be enabled by default. + + +## Understanding the Wordpress example template + +Let's take a closer look at the template used by the Wordpress example. The Wordpress application consists of 4 microservices: an nginx service, a wordpress-php service, a MySQL service, and an NFS service. The architecture looks as follows: + +![Architecture](architecture.png) + +### Variables +The template contains the following variables: + +``` +{% set PROJECT = properties['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = properties['nfs-server'] or {} %} +{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} +{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} +{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} +{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} +{% set NGINX = properties['nginx'] or {} %} +{% set NGINX_PORT = 80 %} +{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} +{% set WORDPRESS_PHP = properties['wordpress-php'] or {} %} +{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} +{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} +{% set MYSQL = properties['mysql'] or {} %} +{% set MYSQL_PORT = MYSQL['port'] or 3306 %} +{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} +{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} +{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +``` + +### Nginx service +The nginx service is a replicated service with 2 replicas: + +``` +- name: nginx + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NGINX_PORT }} + container_port: {{ NGINX_PORT }} + replicas: {{ NGINX_REPLICAS }} + external_service: true + image: gcr.io/{{ PROJECT }}/nginx:latest + volumes: + - mount_path: /var/www/html + nfs: + server: {{ NFS_SERVER_IP }} + path: / +``` + +The nginx image builds upon the standard nginx image and simply copies a custom configuration file. + +### Wordpress-php service +The wordpress-php service is a replicated service with 2 replicas: + +``` +- name: wordpress-php + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_name: wordpress-php + service_port: {{ WORDPRESS_PHP_PORT }} + container_port: {{ WORDPRESS_PHP_PORT }} + replicas: 2 + image: wordpress:fpm + env: + - name: WORDPRESS_DB_PASSWORD + value: {{ MYSQL_PASSWORD }} + - name: WORDPRESS_DB_HOST + value: mysql-service + volumes: + - mount_path: /var/www/html + nfs: + server: {{ NFS_SERVER_IP }} + path: / +``` + +### MySQL service +The MySQL service is a replicated service with a single replica: + +``` +- name: mysql + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ MYSQL_PORT }} + container_port: {{ MYSQL_PORT }} + replicas: 1 + image: mysql:5.6 + env: + - name: MYSQL_ROOT_PASSWORD + value: {{ MYSQL_PASSWORD }} + volumes: + - mount_path: /var/lib/mysql + gcePersistentDisk: + pdName: {{ MYSQL_DISK }} + fsType: {{ MYSQL_DISK_FSTYPE }} +``` + +### NFS service +The NFS service is a replicated service with a single replica: + +``` +- name: nfs-server + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NFS_SERVER_PORT }} + container_port: {{ NFS_SERVER_PORT }} + replicas: 1 # Has to be 1 because of the persistent disk + image: jsafrane/nfs-data + privileged: true + cluster_ip: {{ NFS_SERVER_IP }} + volumes: + - mount_path: /mnt/data + gcePersistentDisk: + pdName: {{ NFS_SERVER_DISK }} + fsType: {{ NFS_SERVER_DISK_FSTYPE }} +``` + +## Deploying Wordpress +We can now deploy Wordpress using: + +``` +dm deploy examples/wordpress/wordpress.jinja +``` + diff --git a/examples/wordpress/images/nginx/Dockerfile b/examples/wordpress/images/nginx/Dockerfile new file mode 100644 index 000000000..ce14e868d --- /dev/null +++ b/examples/wordpress/images/nginx/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx +COPY default.conf /etc/nginx/conf.d/default.conf diff --git a/examples/wordpress/images/nginx/Makefile b/examples/wordpress/images/nginx/Makefile new file mode 100644 index 000000000..d07e33213 --- /dev/null +++ b/examples/wordpress/images/nginx/Makefile @@ -0,0 +1,18 @@ +.PHONY: all build push clean + +PREFIX = gcr.io/$(PROJECT) +IMAGE = nginx +TAG = latest + +DIR = . + +all: build + +build: + docker build -t $(PREFIX)/$(IMAGE):$(TAG) $(DIR) + +push: build + gcloud docker push $(PREFIX)/$(IMAGE):$(TAG) + +clean: + docker rmi $(PREFIX)/$(IMAGE):$(TAG) diff --git a/examples/wordpress/images/nginx/default.conf b/examples/wordpress/images/nginx/default.conf new file mode 100644 index 000000000..af2d7ebf8 --- /dev/null +++ b/examples/wordpress/images/nginx/default.conf @@ -0,0 +1,48 @@ +upstream phpcgi { + server wordpress-php:9000; +} + +server { + listen 80 ; + + root /var/www/html; + index index.php index.html index.htm; + + server_name localhost; + + location / { + try_files $uri $uri/ =404; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + + # With php5-cgi alone: + fastcgi_pass phpcgi; + + # With php5-fpm: + fastcgi_index index.php; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param DOCUMENT_URI $document_uri; + fastcgi_param DOCUMENT_ROOT $document_root; + fastcgi_param SERVER_PROTOCOL $server_protocol; + + fastcgi_param GATEWAY_INTERFACE CGI/1.1; + fastcgi_param SERVER_SOFTWARE nginx; + + fastcgi_param REMOTE_ADDR $remote_addr; + fastcgi_param REMOTE_PORT $remote_port; + fastcgi_param SERVER_ADDR $server_addr; + fastcgi_param SERVER_PORT $server_port; + fastcgi_param SERVER_NAME $server_name; + #include fastcgi_params; + } +} diff --git a/examples/wordpress/wordpress-resources.yaml b/examples/wordpress/wordpress-resources.yaml new file mode 100644 index 000000000..b66bfec32 --- /dev/null +++ b/examples/wordpress/wordpress-resources.yaml @@ -0,0 +1,11 @@ +resources: +- name: nfs-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +- name: mysql-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja new file mode 100644 index 000000000..1aedda278 --- /dev/null +++ b/examples/wordpress/wordpress.jinja @@ -0,0 +1,80 @@ +{% set PROJECT = properties['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = properties['nfs-server'] or {} %} +{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} +{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} +{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} +{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} +{% set NGINX = properties['nginx'] or {} %} +{% set NGINX_PORT = 80 %} +{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} +{% set WORDPRESS_PHP = properties['wordpress-php'] or {} %} +{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} +{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} +{% set MYSQL = properties['mysql'] or {} %} +{% set MYSQL_PORT = MYSQL['port'] or 3306 %} +{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} +{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} +{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +imports: +- path: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +resources: +- name: nfs-server + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NFS_SERVER_PORT }} + container_port: {{ NFS_SERVER_PORT }} + replicas: 1 # Has to be 1 because of the persistent disk + image: jsafrane/nfs-data + privileged: true + cluster_ip: {{ NFS_SERVER_IP }} + volumes: + - mount_path: /mnt/data + gcePersistentDisk: + pdName: {{ NFS_SERVER_DISK }} + fsType: {{ NFS_SERVER_DISK_FSTYPE }} +- name: nginx + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NGINX_PORT }} + container_port: {{ NGINX_PORT }} + replicas: {{ NGINX_REPLICAS }} + external_service: true + image: gcr.io/{{ PROJECT }}/nginx:latest + volumes: + - mount_path: /var/www/html + nfs: + server: {{ NFS_SERVER_IP }} + path: / +- name: mysql + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ MYSQL_PORT }} + container_port: {{ MYSQL_PORT }} + replicas: 1 + image: mysql:5.6 + env: + - name: MYSQL_ROOT_PASSWORD + value: {{ MYSQL_PASSWORD }} + volumes: + - mount_path: /var/lib/mysql + gcePersistentDisk: + pdName: {{ MYSQL_DISK }} + fsType: {{ MYSQL_DISK_FSTYPE }} +- name: wordpress-php + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_name: wordpress-php + service_port: {{ WORDPRESS_PHP_PORT }} + container_port: {{ WORDPRESS_PHP_PORT }} + replicas: 2 + image: wordpress:fpm + env: + - name: WORDPRESS_DB_PASSWORD + value: {{ MYSQL_PASSWORD }} + - name: WORDPRESS_DB_HOST + value: mysql-service + volumes: + - mount_path: /var/www/html + nfs: + server: {{ NFS_SERVER_IP }} + path: / diff --git a/examples/wordpress/wordpress.jinja.schema b/examples/wordpress/wordpress.jinja.schema new file mode 100644 index 000000000..6124862f6 --- /dev/null +++ b/examples/wordpress/wordpress.jinja.schema @@ -0,0 +1,69 @@ +info: + title: Wordpress + description: | + Defines a Wordpress website by defining four replicatedservices: an NFS service, an NGINX service, an Wordpress-PHP service, and a MySQL service. + + The NGINX service and the Wordpress-fpm service both use NFS to share files. + +properties: + project: + type: string + default: dm-k8s-testing + description: Project location to load the images from. + nfs-service: + type: object + properties: + ip: + type: string + default: 10.0.253.247 + description: The IP of the NFS service. + port: + type: int + default: 2049 + description: The port of the NFS service. + disk: + type: string + default: nfs-disk + description: The name of the persistent disk the NFS service uses. + fstype: + type: string + default: ext4 + description: The filesystem the disk of the NFS service uses. + nginx: + type: object + properties: + replicas: + type: int + default: 2 + description: The number of replicas for the nginx service. + wordpress-php: + type: object + properties: + replicas: + type: int + default: 2 + description: The number of replicas for the wordpress-php service. + port: + type: int + default: 9000 + description: The port the wordpress-php service runs on. + mysql: + type: object + properties: + port: + type: int + default: 3306 + description: The port the MySQL service runs on. + password: + type: string + default: mysql-password + description: The root password of the MySQL service. + disk: + type: string + default: mysql-disk + description: The name of the persistent disk the MySQL service uses. + fstype: + type: string + default: ext4 + description: The filesystem the disk of the MySQL service uses. + diff --git a/types/replicatedservice/v2/replicatedservice.py b/types/replicatedservice/v2/replicatedservice.py new file mode 100644 index 000000000..a6ef0d71e --- /dev/null +++ b/types/replicatedservice/v2/replicatedservice.py @@ -0,0 +1,194 @@ +"""Defines a ReplicatedService type by creating both a Service and an RC. + +This module creates a typical abstraction for running a service in a +Kubernetes cluster, namely a replication controller and a service packaged +together into a single unit. +""" + +import yaml + +SERVICE_TYPE_COLLECTION = 'Service' +RC_TYPE_COLLECTION = 'ReplicationController' + + +def GenerateConfig(context): + """Generates a Replication Controller and a matching Service. + + Args: + context: Template context. See schema for context properties + + Returns: + A Container Manifest as a YAML string. + """ + # YAML config that we're going to create for both RC & Service + config = {'resources': []} + + name = context.env['name'] + container_name = context.properties.get('container_name', name) + namespace = context.properties.get('namespace', 'default') + + # Define things that the Service cares about + service_name = context.properties.get('service_name', name + '-service') + service_type = SERVICE_TYPE_COLLECTION + + # Define things that the Replication Controller (rc) cares about + rc_name = context.properties.get('rc_name', name + '-rc') + rc_type = RC_TYPE_COLLECTION + + service = { + 'name': service_name, + 'type': service_type, + 'properties': { + 'apiVersion': 'v1', + 'kind': 'Service', + 'namespace': namespace, + 'metadata': { + 'name': service_name, + 'labels': GenerateLabels(context, service_name), + }, + 'spec': { + 'ports': [GenerateServicePorts(context, container_name)], + 'selector': GenerateLabels(context, name) + } + } + } + set_up_external_lb = context.properties.get('external_service', None) + if set_up_external_lb: + service['properties']['spec']['type'] = 'LoadBalancer' + cluster_ip = context.properties.get('cluster_ip', None) + if cluster_ip: + service['properties']['spec']['clusterIP'] = cluster_ip + config['resources'].append(service) + + rc = { + 'name': rc_name, + 'type': rc_type, + 'properties': { + 'apiVersion': 'v1', + 'kind': 'ReplicationController', + 'namespace': namespace, + 'metadata': { + 'name': rc_name, + 'labels': GenerateLabels(context, rc_name), + }, + 'spec': { + 'replicas': context.properties['replicas'], + 'selector': GenerateLabels(context, name), + 'template': { + 'metadata': { + 'labels': GenerateLabels(context, name), + }, + 'spec': { + 'containers': [ + { + 'env': GenerateEnv(context), + 'name': container_name, + 'image': context.properties['image'], + 'ports': [ + { + 'name': container_name, + 'containerPort': context.properties['container_port'], + } + ], + } + ], + } + } + } + } + } + + # Set up volume mounts + if context.properties.get('volumes', None): + rc['properties']['spec']['template']['spec']['containers'][0]['volumeMounts'] = [] + rc['properties']['spec']['template']['spec']['volumes'] = [] + for volume in context.properties['volumes']: + # mountPath should be unique + volume_name = volume['mount_path'].replace('/', '-').lstrip('-') + '-storage' + rc['properties']['spec']['template']['spec']['containers'][0]['volumeMounts'].append( + { + 'name': volume_name, + 'mountPath': volume['mount_path'] + } + ) + del volume['mount_path'] + volume['name'] = volume_name + rc['properties']['spec']['template']['spec']['volumes'].append(volume) + + if context.properties.get('privileged', False): + rc['properties']['spec']['template']['spec']['containers'][0]['securityContext'] = { + 'privileged': True + } + + config['resources'].append(rc) + return yaml.dump(config) + + +# Generates labels either from the context.properties['labels'] or generates +# a default label 'name':name +def GenerateLabels(context, name): + """Generates labels from context.properties['labels'] or creates default. + + We make a deep copy of the context.properties['labels'] section to avoid + linking in the yaml document, which I believe reduces readability of the + expanded template. If no labels are given, generate a default 'name':name. + + Args: + context: Template context, which can contain the following properties: + labels - Labels to generate + + Returns: + A dict containing labels in a name:value format + """ + tmp_labels = context.properties.get('labels', None) + ret_labels = {'name': name} + if isinstance(tmp_labels, dict): + for key, value in tmp_labels.iteritems(): + ret_labels[key] = value + return ret_labels + + +def GenerateServicePorts(context, name): + """Generates a ports section for a service. + + Args: + context: Template context, which can contain the following properties: + service_port - Port to use for the service + target_port - Target port for the service + protocol - Protocol to use. + + Returns: + A dict containing a port definition + """ + service_port = context.properties.get('service_port', None) + target_port = context.properties.get('target_port', None) + protocol = context.properties.get('protocol') + + ports = {} + if name: + ports['name'] = name + if service_port: + ports['port'] = service_port + if target_port: + ports['targetPort'] = target_port + if protocol: + ports['protocol'] = protocol + + return ports + +def GenerateEnv(context): + """Generates environmental variables for a pod. + + Args: + context: Template context, which can contain the following properties: + env - Environment variables to set. + + Returns: + A list containing env variables in dict format {name: 'name', value: 'value'} + """ + env = [] + tmp_env = context.properties.get('env', []) + for entry in tmp_env: + if isinstance(entry, dict): + env.append({'name': entry.get('name'), 'value': entry.get('value')}) + return env diff --git a/types/replicatedservice/v2/replicatedservice.py.schema b/types/replicatedservice/v2/replicatedservice.py.schema new file mode 100644 index 000000000..712ffd315 --- /dev/null +++ b/types/replicatedservice/v2/replicatedservice.py.schema @@ -0,0 +1,91 @@ +info: + title: Replicated Service + description: | + Defines a ReplicatedService type by creating both a Service and an RC. + + This module creates a typical abstraction for running a service in a + Kubernetes cluster, namely a replication controller and a service packaged + together into a single unit. + +required: +- image + +properties: + container_name: + type: string + description: Name to use for container. If omitted, name is used. + service_name: + type: string + description: Name to use for service. If omitted, name-service is used. + namespace: + type: string + description: Namespace to create resources in. If omitted, 'default' is + used. + default: default + protocol: + type: string + description: Protocol to use for the service. + service_port: + type: int + description: Port to use for the service. + target_port: + type: int + description: Target port to use for the service. + container_port: + type: int + description: Port to use for the container. + replicas: + type: int + description: Number of replicas to create in RC. + image: + type: string + description: Docker image to use for replicas. + labels: + type: object + description: Labels to apply. + env: + type: array + description: Environment variables to apply. + properties: + name: + type: string + value: + type: string + external_service: + type: boolean + description: If set to true, enable external load balancer. + cluster_ip: + type: string + description: IP to use for the service + privileged: + type: boolean + description: If set to true, enable privileged container + volumes: + type: array + description: Volumes to mount. + items: + type: object + properties: + mounth_path: + type: string + description: Path to mount volume + # See https://cloud.google.com/container-engine/docs/spec-schema?hl=en for possible volumes. Since we only use gcePersistentDisk and NFS in our examples we have only added these ones. + oneOf: + gcePersistentDisk: + pdName: + type: string + description: Persistent's disk name + fsType: + type: string + description: Filesystem type of the persistent disk + nfs: + server: + type: string + description: The hostname or IP address of the NFS server + path: + type: string + description: The path that is exported by the NFS server + readOnly: + type: boolean + description: Forces the NFS export to be mounted with read-only permissions + From f7940f1e6be2e007a0d91040f2e55a4ddf2dd3e3 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 16:54:52 -0800 Subject: [PATCH 02/33] Updates Wordpress example --- examples/wordpress/README.md | 27 +++++++++++++++------------ examples/wordpress/wordpress.jinja | 11 ++++++----- examples/wordpress/wordpress.yaml | 6 ++++++ 3 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 examples/wordpress/wordpress.yaml diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 14cbc2ae9..96627374a 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -10,7 +10,7 @@ First, make sure DM is installed in your Kubernetes cluster by following the ins [README.md](../../README.md). ### Google Cloud Resources -The Wordpress application will make use of several persistent disks, which we will host on Google Cloud. To create these disks we will create a deployment using Google Cloud Deployment Manager: +The Wordpress application will make use of several persistent disks, which we will host on Google Cloud. To create these disks we will create a deployment using Google Cloud Deployment Manager: ```gcloud deployment-manager deployments create wordpress-resources --config wordpress-resources.yaml``` where `wordpress-resources.yaml` looks as follows: @@ -32,6 +32,12 @@ resources: ### Privileged containers To use NFS we need to be able to launch privileged containers. If your Kubernetes cluster doesn't support this you can either manually change this in every Kubernetes minion or you could set the flag at `kubernetes/saltbase/pillar/privilege.sls` to true and (re)launch your Kubernetes cluster. Once Kubernetes 1.1 releases this should be enabled by default. +### NFS Library +Currently the nfs-common library should be installed by default on Kubernetes nodes. In case nfs-common is not installed you can install it on all Kubernetes nodes using the following command: +``` +gcloud compute instances list | cut -d ' ' -f 1 | tail -n +2 | xargs -n1 gcloud compute ssh --command="sudo apt-get update;sudo apt-get -y install nfs-common" +``` + ## Understanding the Wordpress example template @@ -43,23 +49,20 @@ Let's take a closer look at the template used by the Wordpress example. The Word The template contains the following variables: ``` -{% set PROJECT = properties['project'] or 'dm-k8s-testing' %} -{% set NFS_SERVER = properties['nfs-server'] or {} %} +{% set PROPERTIES = properties or {} %} +{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %} {% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} {% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} {% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} {% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} -{% set NGINX = properties['nginx'] or {} %} +{% set NGINX = PROPERTIES['nginx'] or {} %} {% set NGINX_PORT = 80 %} {% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} -{% set WORDPRESS_PHP = properties['wordpress-php'] or {} %} +{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %} {% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} {% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} -{% set MYSQL = properties['mysql'] or {} %} -{% set MYSQL_PORT = MYSQL['port'] or 3306 %} -{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} -{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} -{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +{% set MYSQL = PROPERTIES['mysql'] or {} %} {% set MYSQL_PORT = MYSQL['port'] or 3306 %} {% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} ``` ### Nginx service @@ -126,7 +129,7 @@ The MySQL service is a replicated service with a single replica: gcePersistentDisk: pdName: {{ MYSQL_DISK }} fsType: {{ MYSQL_DISK_FSTYPE }} -``` +``` ### NFS service The NFS service is a replicated service with a single replica: @@ -146,7 +149,7 @@ The NFS service is a replicated service with a single replica: gcePersistentDisk: pdName: {{ NFS_SERVER_DISK }} fsType: {{ NFS_SERVER_DISK_FSTYPE }} -``` +``` ## Deploying Wordpress We can now deploy Wordpress using: diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index 1aedda278..8c559a0d9 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -1,16 +1,17 @@ -{% set PROJECT = properties['project'] or 'dm-k8s-testing' %} -{% set NFS_SERVER = properties['nfs-server'] or {} %} +{% set PROPERTIES = properties or {} %} +{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %} {% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} {% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} {% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} {% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} -{% set NGINX = properties['nginx'] or {} %} +{% set NGINX = PROPERTIES['nginx'] or {} %} {% set NGINX_PORT = 80 %} {% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} -{% set WORDPRESS_PHP = properties['wordpress-php'] or {} %} +{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %} {% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} {% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} -{% set MYSQL = properties['mysql'] or {} %} +{% set MYSQL = PROPERTIES['mysql'] or {} %} {% set MYSQL_PORT = MYSQL['port'] or 3306 %} {% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} diff --git a/examples/wordpress/wordpress.yaml b/examples/wordpress/wordpress.yaml new file mode 100644 index 000000000..b401897ab --- /dev/null +++ b/examples/wordpress/wordpress.yaml @@ -0,0 +1,6 @@ +imports: +- path: wordpress.jinja + +resources: +- name: wordpress + type: wordpress.jinja From ebf3a33505a51a5dc5b6ac992587408b021af1cc Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 17:08:29 -0800 Subject: [PATCH 03/33] Change hardcoded type urls from leendersr/deployment-manager to kubernetes/deployment-manager --- examples/wordpress/README.md | 8 ++++---- examples/wordpress/wordpress.jinja | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 96627374a..3778b0dd7 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -70,7 +70,7 @@ The nginx service is a replicated service with 2 replicas: ``` - name: nginx - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -91,7 +91,7 @@ The wordpress-php service is a replicated service with 2 replicas: ``` - name: wordpress-php - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} @@ -115,7 +115,7 @@ The MySQL service is a replicated service with a single replica: ``` - name: mysql - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ MYSQL_PORT }} container_port: {{ MYSQL_PORT }} @@ -136,7 +136,7 @@ The NFS service is a replicated service with a single replica: ``` - name: nfs-server - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NFS_SERVER_PORT }} container_port: {{ NFS_SERVER_PORT }} diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index 8c559a0d9..35c6a159d 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -17,10 +17,10 @@ {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} imports: -- path: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +- path: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py resources: - name: nfs-server - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NFS_SERVER_PORT }} container_port: {{ NFS_SERVER_PORT }} @@ -34,7 +34,7 @@ resources: pdName: {{ NFS_SERVER_DISK }} fsType: {{ NFS_SERVER_DISK_FSTYPE }} - name: nginx - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -47,7 +47,7 @@ resources: server: {{ NFS_SERVER_IP }} path: / - name: mysql - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ MYSQL_PORT }} container_port: {{ MYSQL_PORT }} @@ -62,7 +62,7 @@ resources: pdName: {{ MYSQL_DISK }} fsType: {{ MYSQL_DISK_FSTYPE }} - name: wordpress-php - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} From ef7e06269c457f234c2628bdcbe5d72c4ec03e59 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 17:13:04 -0800 Subject: [PATCH 04/33] Fix typo, we want to deploy wordpress.yaml not .jinja --- examples/wordpress/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 3778b0dd7..2f40c58df 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -155,6 +155,6 @@ The NFS service is a replicated service with a single replica: We can now deploy Wordpress using: ``` -dm deploy examples/wordpress/wordpress.jinja +dm deploy examples/wordpress/wordpress.yaml ``` From f337d6c9032154dfed5e7976b49ce3dc7fe99efb Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 17:15:19 -0800 Subject: [PATCH 05/33] Fix spelling --- examples/wordpress/wordpress.jinja.schema | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wordpress/wordpress.jinja.schema b/examples/wordpress/wordpress.jinja.schema index 6124862f6..215b47e1e 100644 --- a/examples/wordpress/wordpress.jinja.schema +++ b/examples/wordpress/wordpress.jinja.schema @@ -1,9 +1,9 @@ info: title: Wordpress description: | - Defines a Wordpress website by defining four replicatedservices: an NFS service, an NGINX service, an Wordpress-PHP service, and a MySQL service. + Defines a Wordpress website by defining four replicated services: an NFS service, an nginx service, a wordpress-php service, and a MySQL service. - The NGINX service and the Wordpress-fpm service both use NFS to share files. + The nginx service and the Wordpress-php service both use NFS to share files. properties: project: From a893ea44b45a2314bba61561c8477b8fb14f2e06 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 17:26:00 -0800 Subject: [PATCH 06/33] Updates Makefile to use generic Docker registry --- examples/wordpress/images/nginx/Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/wordpress/images/nginx/Makefile b/examples/wordpress/images/nginx/Makefile index d07e33213..3974d978e 100644 --- a/examples/wordpress/images/nginx/Makefile +++ b/examples/wordpress/images/nginx/Makefile @@ -1,6 +1,7 @@ .PHONY: all build push clean -PREFIX = gcr.io/$(PROJECT) +DOCKER_REGISTRY = gcr.io +PREFIX = $(DOCKER_REGISTRY)/$(PROJECT) IMAGE = nginx TAG = latest @@ -12,7 +13,11 @@ build: docker build -t $(PREFIX)/$(IMAGE):$(TAG) $(DIR) push: build +ifeq ($(DOCKER_REGISTRY),gcr.io) gcloud docker push $(PREFIX)/$(IMAGE):$(TAG) +else + docker push $(PREFIX)/$(IMAGE):$(TAG) +endif clean: docker rmi $(PREFIX)/$(IMAGE):$(TAG) From 259a468da71182e8130e02837526e2eef7665232 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 17:40:43 -0800 Subject: [PATCH 07/33] Adds picture to README --- examples/wordpress/architecture.png | Bin 0 -> 33703 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/wordpress/architecture.png diff --git a/examples/wordpress/architecture.png b/examples/wordpress/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..853039e63312eaeebcbbe8cdb1c24f7c59c3f88e GIT binary patch literal 33703 zcmeFZcT|&E`!*WH!Lb0Ns5Ai;1yLj*p-7ccK}0}7YA7O7LvNubIE*6FRHRoydM^o` zpaMbZHS{VVHHKbNz8&VB8Q$MI-#P27b=ErneEb7E$$p-_?|tuk-}iN0`w4oep~8HO z>lg$AVOG6&M+*Y^1^mfa|0@&tWjoUA1%dcORPQJ~_8eLoJ?d+0k*G+$q#j?US_Pf| zT?qcX}b;0gcFqQ^>JX<&X$JJG_*@%XogzjYonEW7`U za?zC&DXIP`7DDfTZ=oa;?Rn*l9J@cZdGDccCdtvT%F4Of*@VKmov}H&%p7{3{x?5Q z?8R*YzP?yO-}&fTa!)5)I&|0(xAvZqWv70uG)!S-+mBUlz2-}}%!rMiAsUIK0hW)H(#GhN*_!96_`p#NNzh& z@9sx7ElkPLd*e6EhFD~maC@zO?TN*ECgVe9nTcr3PLy^sw-oAgH;S@bYpkbd8YAjF z`q>h0XD~pjtvQkpqcn+WMyoR@3Airw_}w7$-S??|148)U#&dqvy7VL@Y7sH zyYgv`Eux%xIl?2hczAxXp!!q6Ad#@7c>?slDfAW7#88UYg3p+1D+_USX6Sl`!`gPt zY_VGECn=ZSALZq-SmWYiBw3J#`MDE})2g7(64~QjG!4^mh~&=GCKFg|s&hX(moFmj zF52^h)0QUR6(h;5_{Q0Z^{r)HYdbqb1-tE^1j)FXuV3Od2Fba%U!G7D_8I3|migx) z*O+%8T)=#6%VYkzScI!qr zeXHwG6RYk51$*0n2C?DZ)TYM|Pj>+k?i+4DrrZVd(XN{&L3Zf|Ep z1sqptOst&P^=4Kcc*QjSIBdR3Y@9mAdR%-`R?V*m$gfYe?KcPi0&M& z_IX5hWO1MWqOtfPm2exAp^*{as?pOB$SuZ)cNpLFl}vtD=N%=c+#WYs4`hdpdYe6! zob`~c+u9&4=DF6^p85NmEH}Rwv7&rivWKn9HdY&Wq65P@NV@BYWz64CEF1QaU0U}X znbeEO-`xgCGVf_02OU3XIuH{M&z@PL!R{q zDwaH5%g377jUV~@pE8UDm4-R)S3lBoLH6N) z9%xWIbh<6wk41WEP+e;HX(NxKNy<+DahK_Q#VcT4&}N+l6G{f^SRs%UhAXKI_cD7k zT%kKdEp5|HINs9sREG4E1WM)V5KRayi?faohMZR46`HIi&TRStQ!(E<$$U!&BiU$I zAB;mSBNpw8CUfB$qqA++_Znr~;#`fR@wA!>r>_aujxFZnCcE1*>*i)!3UO`!K9NAk zP%EfF4ZgpQ^-j0>`9_MBT7M+^uDEENPMilKN#BuFQaLWXgz2Cbve;VtB8I|xOlzl_ zh<}gq4%^2)L{H*z-fIc2ljwpN8T&xJR0GT+uefROY#+a-LS`)CxvmU>s->x{m2<1HgCQ$?qpZFhk>Y{&D! zI=7WyqE9=XpK_VRnfCj%u&!Ju4_BJ%SK(XHyTd=k+SItYxTA42|1V=T)+?A7JHbh% z2X0%aE;S;Cb83I#dh7@fYJJt6F#N;@_DiOn!p4kwSbVK#dnDns18tK0I$3DDlb9qV zxhJx_IwBP&y)eKonNaif8cD&ff|St`w*7PI>6i6cuj9*m!2V2w7gLXRZy~f>?SkX= z6Py+XU&wDNOe|af(Z_nOEY;@cgp{CT)nJPcpHuT%vfM^1J8svMP4_}NsKeZ}opYUUZvQ#M8#||a;MLHj{q+)H7oc}i{Wpy2znw~z4D#cH zKuAfUlNABZ-e z{kH{hkht?%>iZ8rQF0fi^e}$^(f_+U(LE~eylupO!~zZOupM}W0DU0O{(1Pp-h%7N zC3J^>^5%N#(f^F;zxuxKss{!OQ|c8crnlJdN{ZwlJ!FBtcO6!;|G&S}euKxh_WSPX z%qCEGEkZKJCnI4lzb^9jzAV$^P z{#{ozLTlVk;EQ>ftF7NR;?h3xBAArZgl$!+`r*okcKlpUqzlTvzy7Akw{e*(h*S|{ z!O_7UuFhQ9%`}lWt#L(xW{`}_+s0(Q2;_AisWyS1Lz0y4$mx-huqo{|C~tYkCzaC< zRlv9BrUm#(zK=@pbNfF!p4QaRfhWDSHSGN}>n3T0KKEe-+SY$%55lq!AGB3+G-LZ) zvu$IuOJoYgn20lRN@Dt&p^ebi$^~QbpIX!BMVN&XFGn*qrz;XTnd@Ve9v4&&3^LdE zp2+HDdSaV%C!$nvWi{t$cQu!P*U#6}mo9VJi`_2BvG6ddIw2`Bn(C$JZKTW`WZ=DH zE-w<%CbiI0e6qt-CPI1>?`Gp*!zgNERTI`7TmJ~XDv{yFT%V;r&-l7OjAVUlmX^r% zT8O=^c9XBpf%z6a^et&PzH3O40QF(w2FKy04<; zUV7o(fRr2yWp#M;rzXVfSqoc;n=+$l+~J)$;qov&Tiv`I1`hRL7sPW+CPR$rw4Dxf zz2}2UwbYij9J`KC)~U)NR~wfaR(p97io9;7QVxT@0dsx-sc}v5`HMRk;{Aw~a|z?+ zsX_X?W_Nubo-Cz2&GwIx*LOfe?I9UW(;DKN!YhUOJeujiWa%+sjopO3ncO-ZvPn%C zSyC-Np0uYI30s$dfDm`0r1~R6jEMO)B&XAfnl#R6rZp0VW26a!am!FQ98j!d516+8 zt+C^dYcZxtTTZX440zqv0x3r#^Xx16Lu*bwO0c#nXb| z-co&@9UWlPn%Y|J;EWcS<%W_fD$a?wF{yXxtq{3OD^pb?(0*dU*%&oE93i~*gt6#R zwoqn0Maf8JKZ52zH4IgrmX`ZbFcB~l+?1F z7*oFd{Ef@i#S9fHQOTBGI@v;tFNBengBhjV{>#RAk-i|`(jwUM&Hicq5Y~&Ss&$i1 z+G96OwAhL;mPSjN&LMabC*E17C9l4p%bzrPJ10KBcC6Q$067dYay+-c&8+dfT|UQv zGUHX8i0Q6Lm7U1=NKl4pUQi@o44%;KZK_jQ?V>y_5_fMW_PJP3f$f=SS`PEVyo$qx^awBeftMIDpO{A@F6H(uL z-pyOHf4#jG(Rr9NcILG()WS=)oK-7>T6SqHbFS3)!veANa&}Mh zpN6vi(*vQ($ocfOd9BebgPAyI=LeBvZL&%H5}$hP8oNQj8H$E0N}vQro9{6G268Er z9*D3M{2G_+{JR10u~Cy=Bz*enc;3}=tg)OU44>*8Wl4C9Y28+h-piQP^@;OL9EF4s z&V<%=7pj=&j$EYK{7l9jmX5PbS1;NgP0fDG*IABbdQvID*c11PzIsCmLl#~0?-o3- zr$-w$5wzAMCHT8}qNg2Waj=RJyE+&2u)bi)Rpgg|ZqAQ3o94*ND0~zJq1r+d$+yMN zq_1jp?`;r-ORICiU`^!|EGHw!ROQfmqy1qUkJ0zh3Vubu;Z-UvjbLppqeTK(_F;AA z#_sqR4XCJ<0eICjr5bF`Na$;sZb*o77Snlp{`e#s22q1DYKYfqX=j!(a0G3PuSUO% zYJX#MU92-qS<6-~T@BU46Sx*Viiuj^?Pk8yU&f%ng2AWMVdjL95y95KHl!DIJqeyw zk~6i8H6 zrUF@Dv1t9qL`-Oeh@gEw14qE(wju1b)|!YSKPV-|itHC1ifxMGEqR-sC|Zu@ zFF0Uqu$o-b$h(Z96pdvYNYjJrvN3(wg;ayzn4epxWMmkCl*Kd2`ci{MJ;^YY^cz9| zE6G@t&-BFbh^VDw6K+~QRM`f0DD7s92(n#@Uu*3KiV)NFKJK|qh8b8z9ZieSXmXp8 zw<=>%!rS}m2DfFaB^&Eb?Ya1@XeB1!V9-Q^<>m|`P9yomm0KOb3B-$Ad%ie z@jWM)UU)4GV-3k*@~R@sn+B#n=O}$Xt=~@Uc|=)&N=aXD!qvo`Q=w&^ zbsVDj6uCA_wNmf?vcdQL6W1u-Cl*?a4<(2^*I?Xo3kY#Rwj1j(ijF?M%&Rwa{?%52 z5u<2+o8HQ)Z=TSe#o(qpHsTU-$9BL}8{aSIF3q3XXz)3um`v3m)y!BrL{{e_;&iHH z)I&t&s9^u-B=oxqkxu~%| zSW?=$dpho}t#Ag@lh)o=sm;`*>_pc%OspkiR6*d`aJ6UwJn?iJ-g-3>;gX@oIc?SL z(Nf#wCWM?nQD3IA&%dkMHUafuLU(!*$!Pvmt@YtqtHg8x!(9Z0@3?T@tt`Q{eeAN@vvz ztWDCZEusXtOl6}`7!Xx!?VDV=wjsrW95+FGyJM#6fhkZJ&=fx||$ zKxa8FOF+^pqtIdNajS*0T$$Nll-pIA6L4wC4=R|X;K}z(2LtUI@^z?T#EDR61D3{)SO0Msa3jf$3K>abx6?K-S$Kq`l!!S z)S~wa7DVz$727;;#O!QxX+A!EYb#^>ckwRTywb>=q|aA?;l8)t*I@i=nu(k1Xl`;$Ek`tOx%*elzlgz-Ns=4; z$Z{remlfH*+!CZ_Xg*ryA_aT9dAZ+usa37?zyt=r5@5+j%-R zCiWaNx!YaL-1wWP(MWR!|L|)(zGJ`KrURM4$*Y8%y}W7)tXuE)KrdSoGpZls)5$x7 zU*|O@oso-ScGpHnhYvs*y=~0(bg*Pl;>tegv6UtigERMnF%0`0`75IX=rkzrGfV~` z4}xY*0^`|4MCf=>om&hmM=jY^Tcf^pF#Pq7tdT@0r&L1P9+@d79+TthLp4X7B&3d z9@2!{>+3)35m#(AnNiJtnns}~~yEKD*z8Cl}zEp3z>##S*+fYNTs7y(fhcS;TjzhZLAXb2M0 zV9t*e@oYUfng}qOGAUkqwQdOFT{Q$hgckb(b%2}-55h2Lvx!PTGy!bYh;Kv_OZN8m z;XnXl(qSO^K1c|+ryVGI8}T32eAKx8%g45yid8w7>&YESu9FE!dE~tMt!~`3#6HgR zw;NP4a752FS=5jy07uztCxC{TzO&>KDTYe&?(l1X%Js}e0n4U0I%hjM)}G#njp=|7 z!@WirB<(1>)XqIl0F}uuZm17C&wVD}?ZAM+Rg=R&Z7-Vj;e%(alib|V%p_yGmAfkj z+jjsILS3C2^AsxjcN!E-<7bUrU5h{IiKzH`dN}9Tb%Ri2;;Y!Uq!!K4F+%Hn`UNU+ zdTU^>eu~gzK1CwArgtEJJi?#BjSY>qUM|5L=R>o9#G{R^!f#IwM4 z?SBEd|Daw6aWZ;O4#Hsv@j|dBdIjkC&0omeK`8J4;!gjMh}&M>-7F2$4_*6(#HNkY8_N4CWfhj($iLur20hL#18)H1vl<{Y%Cq;hVpsc4d) zucZ3WK2C29DUsod$LFs#j~zih*#TZQi1&UpE5Gh$wt$rt=9IG=xIQ?Z-CqDW-TiJg zFXZr)uY4rt;kpDIN1X7!dj`;EI^rhk`#tM&02g;hxzFFX?iSi55coz1de=vEd$8!s zTn{(PS2`~18R2{r-3dO$s3%{{2a}&=)B^JGFV?C3Y;+uNC(QFF8~1~;^>nqunhzB$ ziGGEPB>*r#mR$V<;TYz?yY-Y(|6H+Uj5fOb5=fU6WU_V@L>s^bfPX~*vVjo}?2(}0W@!J@Y)8i&+ywAEY*C&ZyXmO@2E}+h} z<>Y59|HQ9}?Cf>}AhJne@cBsT&!Erqb0+I#CT_>wJro90sH>-kZ(I;faPnqwH~nEU-zqfVjDGGV)O{y%~~CYDwGO0ouh3k{P;PZw)G+n#!&)A5GrY)ySO4QnF+*o$91BGfYn| zzlybYW(zJY89A+91emwW04Zb-=aASXZ7qrVO`gQJQv0l+y95T|`(0Ui z9kuxT9B+p_pehQj2gi=;T#NT1$z)(_M8KF+4n6p~`bRmq9|Ur>=eo`{tE2Wm^6CHi ze_K=T`-W*|A?ecpy=8F|Ydf=qD=+c=I=uh&$QdaeH11J-+8C(9AdswcZJ;$ZooV8! z<5BvB#~FW=(|<#*M$@Yka6xn*^?_tP;qs^d|L_xxj*CH3{;+`nKZ4x)rH2lFK>Uwo zFaVVU1ajs9z4iw;i1|M^_&+xI|HThV&7#hL1%rMoRnqGXr#oYci*LED#hRm66rZ>3 z{)LhT;d-f3xx^Va$0PI$kCTq%=(Zmk`2_!KomhECMd#$v_CGtb=;R2lBI4f~SW3WH z>OKGhV|-Xsnh#L3#kb8i+FDtZTe)fO>Dr>U>R z$yo#^JMJVcvpbaRL@ACr&y6~ck^Fr8yr*7jORi;pkN~V$!phW@i-@GVIj@HKH3^vhOK|_#0-8(3h$}6OH)YXfKsHo?Xwh^y}4nrO)2}r;s$7F3=2QFJB z_;kJgB^=ke!d478^lOsyFGD#h-5RpGkh&O0<%tjm#)Y$C95;7orWH40%j*C$w>&ns zPyW-%Z&hx)_02znBF=)8Sg~z&?#p?2!+E&<+npaRj?FSp8i5yK$uck6X@HwC+u%c$88)IXh z-_GLD#DZ!EJ9e>VN$FE!&ARb-dIccmJ}dIW1?F$AUt}8m%c7m} z>Rbv>d(l7yus)<5Z-<46xF@={wGj;i3f=mR^YHA5jpfRY=Pf14aLK3ZG5X<-O*xW} z^-R$-K!ss)D*C6#=GQ?wr^oFwO)%JcAJxXwU5ND{MdNl9(Th23pB;Vo3O>Jjn>Ie? zJ~g&FDF}<em|2d%MS&W^@(%1_A0eN7H;a3%^K|% zd%Jr7-44f^L0u^F?izIb@Hx3>KUOm`GJ0%xH+*J*Q*J-iOFrEsDZ#*8a`&HU^p2!% zY^bo>S^Lgpg`=kO`z?3#+bxQn&BMU#MX~TmPPOPR_vT(}Ssao0R=KIbbk3O@z z#aA@-nj1$WtjLHxP_IuME5{2i83R`O>6?7Xpe%I=L_XW2xoFaqPs(hR;8Sl^KZW`mNQ=WXz1i4q+I2Z_b z#8FOH#{x>8<5{lyMO!w=Elmj;s%X?_yu9oh^o#mUkX+?kbIE;&0(T&+_)Sbgk@%xS z#rR~c0dpdw&(dKrkr%vD&fRMs5`g{x$GIFFsm#1LmfFF5*TO{lmJ8T2v^y$OI??t1 zX8(*~B-cg0mDuqC9P&fFl(Bfvc z?Tj%p5Ic8NCCq`kP{xx1U2LB<{T{L=BC_QA^W%Brw57ak!jbo)3-m`K1+R`* z?M35aE5=?TnX_`?)O1?ar>}6<@2^-pMDi59zM%(}53C1&%$ExilwHd5PjgF}8Q}ii zhL5()5XN=sP{(A#@j2<|i?hA}LwTXP=(wI@JbvKWS#}tM_Fw}v0Zq#++-Oj3p|5!5 z5Uw73sp&V7K^x~57Z%3H9CkxYiRkS|wC)lps9OICGLPRhfjoRzFdx@N^qFF*Nms9r zIm#h^D6!d{cyu~Dxi+N;ARcFx!yHU@lXyI+mVM)noqrry7M+28pd=YD9bMm|OoBkX z^*SQDg3YP*aLvl5PhzY4^wDWqDeFpe)V?Z*iz_}#J)w*`m(%(y3`kr8>@cc#><>Y9 z9oC>N>ce*BJwzY1HRw$DHODIntFdc|ts4`@Z_FiUfe$R^fB5oJF>zT?n@8k;$KD!W zYg3mTU-&xKPmUOi)Q>3skZq%i#kG1g1C7Z)@yriD`Q>H!b`K~tZ<@W+nf2LHG~^hS zTWPezRrY=WarfyP`~Sp{TSTes3{xC#qjwaQ?kEt~2oy@6`1S+Xi4x}jxX_Q8oZMVA z73`V5--6!dX|OrbD<8}1uKBP_V7&*}_3nGxk3`E*PN~VGf~qA|mD6*~_e_1YGI)+EGn~v@Xy_$q10*~rN*?WRPASpS zpct2=hO%9n4}pcl=b{G{+~0lqM&Ba2|FWfb5SUS`g1s?K6X!oJCBDsdq>%VZMi&N! zH^M{?cFn6A-^QJAPSwit`wuZz%f0>!EYSb@oN#UnnyE1L{ho|?CaAtX+l^O!nY1|2 zZTZ~ds;b>|PzO??BKYT9TTRzT;DdFTkKZin!4xqKX6=X0&NL4`fGAxQRPH{*so>H} zEb+5@`K9$>pWAQxEc-MwYJ_~eRx+n>ME?D;D)>*R%bPZ^)kDR+thKyxXl)IT^O|0%QiL#fRd} zXT_S8_tO*qBr$UUOa>>MR@E~(WTfac(Q(f9fFS^oaVCb2Ej-lIqcH}?fpw(xpZsY( zr0obR=k5{u&B5N{IxSLf>X!9i25@5gZN9q|ATR8t6g%wgwuAO->RgD6v(R|`G53b; z`at6#O0w^R<$;+R%`8UyQM%fu36-ez7-w8=K>>;`JCYT9r_tdTE~iZ&E`sIeL4)vW ztAw0$hXY_k%45uawtHWP#gY$qC^eo0+9%himlBiKpw&-z>Xn$wZ>}Gl3WS6dNQljv zJI&Nm=k;WU9s8XHPiyS7?tlsIDc#ZSp~fwvOV9mrz&4%k9);Y}Khn*^X}#SQ=SoMu z*0awV=?u8f)`3Rat{tBRk4Z>Sbf4`_91i?&z;bTSI`XMh%I;A`0Wy^Q4wS0JBH270rKPpuO`x81x64mD&P=vZFm zQOX{x=gv#nS=p|)ys9JCbX5B^K;B5Qro$j2OEEFq?!Nlcb6_I-JN+5lk<_)L$=-vi zVe5D5eEZ6enKy+&O%5f{yz_1kf?=BGv>pP%^B<>^xMAMVEQW6Ds5v!X;5$8xJOjeN zcExTKUnGU9OB`I;u;WXd5tTU5q^}o;9+jUnlAeq`y5vRaOz!h(ASEiY{zqb zO8BJWUbQ?_bG+5XT}qcs0n4u$BBwZ`f0bh9C5x-%9Wc3DYbOB7)b_r$mKWGUP)py% zgB6F_fhI$S(8M$WWWplrpgxlb{O>lsU|l5T{>c`hBLSH{HE#X0&2-%;5JRL3=0;R82$e>PYC1&c!6Uk{2XfhJ(a5nUz1MiiZs4Rfp{t z_l*61xnCnyoam`2K>`Jg{xDC`23D4ueTv&35E4Lw<_e94%ROJ#gTu)vvSY%V3;|nMraaGKolsf=SBk zj3A-#O7aZrj_6u+z?F+ywAhbf@`0A?XQF-ja>LeCWw#_DBcg)`IY{XkBr3L;A>NGk zxM$=G_{s@%cg=-mJe5Og!s@0NU#oII5TOg6iM#ScYi755v$EnJA=J2!NZGJ7uYsX08F9b)W2_+O`(cZ3jj1G-Mgl%du%l~a_-VX-aB`~37vaQnna@{% zSW-mt`xK%pPfZ=e;SWUWR|=Rt`{$t7lB|<=2amrt4SwztxBO0{e%e{gM}QWD^!5Ak z^W+P6fqYZCST#8L4nCz~{i6a`D5<7PIz-7$&Z;J=EMNL)xRQr26eU@AmR5@$J@Gvz z+;8>8(v~9rFRKWg!n;{XLZ+p|&2fTEIgeP=Ud}`ZSUWO`_SFLpD<_V9*(RnT5bULo zwHS*=NIZOyCR!nWtH-VmFz8Xk?Q|-QBppt~qYD=#@H>xuIR=L!k2^b@?r^7C=tboR zIIl!1j(-zukDemu<&GnnZ_yKAfQ*th$Y6$_O|vaai`g$87K{7Cc?#}b*&1EA+h46= z-E-74p(fg7m)kU4z+~RSsq44aB2LeudR*i47R@V z3Z9{^Y29qwWC;43IeU1+MO9BqFrMto>{jm;k^h*K_1V1fPWTaI%6jgKvc9|M+c#S z!n0z^S*%nVXQ#A_^e)&&`cAxwy83G9wEA;ceJE!G>BH~6FUW`>@3R5KEIxY)<{sgs zZc9s`H`(gR`_OIi+Y~09c-L6inB~F((2J6(Hk<+0r!&-!k*5G(lFbdH?SuquD5@>Z*@{x z8@GNtVEf6Xf}~e*Z*6a%+j}IaR?gm$T#)gqDu$QM{nCPJjt(hCTB-%dm#xq^6kzl6 zY0g*DSQ#ci%X$9xq^BKqZrX=*;eK6z6IumGa?|mRu2Jp0;WWjdEm3)au>en~E@q_+ zz`UK0O0mfb)8UaL5MktMRSwgW-9hX-ltSVI{Q1g}uUeH6OR(RX(SM93+2=1a*GFx6 zo2X6`{}5hb8Id>n=3*Gjuw_VqwDRG1IICtB)T$a3xh!mi{H;^dpF89{nz7Z@X<0oa zmKD0(5E54=VuR{eKOwiK$j#UhL2YIt&pbK~FUZ%UIW~qt6Z*WN+JCpb2mw7p&Qq0A(M^~zLnalYrufG&)6x|k&Hyu#jb5?!A2&LDKYHjLOvJS_q3 zt6oPn9D$OINE_RR)XJ%VQvjY%5m)DyHJd8s@}B_`#Gi&DHu?4aB4`ANEeh%yj0-yU zhY0>4eZ9i%Bmf+Z!O zLq12HP_@0jrROaxf&9GAL`GYTsX+%ZXiSTAaSlYpp|vxU}V9WPF7&JmFaZPNAW|DlGrh81*^ z&PiN0KJ%8ESoq7M+gbsl`AUW>0ljYGDP+Txd8CMO(q-GHy>H2Rm0998FI|9;xt)05 z>H!{`YBE=T1|89DX$yMBJb0IL#W<9YQLgGu&P_ zEq%AlW|{ypane!8gmsL^yg@=%I;Rj~2=V|i`H@p1J&03s=hN!Y2!y;KG43b1N8xK* zb!iFuVLBvx>zzj1S|u&97%K@`D}}E{ADZLq>Ay@ByXC0KSY*U=KY;8KFvBdBq%~sc z!xV1Ak5Tm0NMPPBx`twJI|X7as-%(TFxTu~B7YqSi*nZB-({ zw6wKg;Wm;X3~*at>K?PEW}i`ni#Uhqx&IWY@2arXye)65i$JVL7T8BC7W5dt%sv`r zqHQN-%C?61Era`y_wbzamSZ|V;n{?a?oXH2d|ilMZ|I>;&S`5G0O>EfPij7YQ)^2=Lk&b)8ekMRcK^YfYOkI#Y_n(&mlepWcpYR51t zGvU2Qyk{~xVy0X8$YYA%aw7%{)R9^fWb^K)+cQiV=@K~@rYAnO_wMI?)$4PKwf7N; zzm7aQnX_`JCIg)9z}#T|riLT*8x|lwov8OKNr;?L0SF^aXU?!tt?Oqse@kY`(ik^F zTgD;P`7|DWztuaK_0)ZW54=asAGljIM|t_D)1ehv7iPlxT!LO2<(zTOYdUgrXZfRn zgR_E8ff7f!X0;NILW9Z6t!vE(!sO zsuQ$1Ypeo-%~&$n3A^pnEzMisv`Xo(eZ_@s%THTPd9Ak9cgJ>;CS825qs8A;^j1ho zFaSAy%8dtnVz%qzetw;&lX>Tk%+9Q2#iH$~J+mD3bHv#wjf$y{611;&Ix8!QI}up7 zxfU13A1;ivZ9^Nj3rXT8+9Wg|ZC zxI$(Wn(vje0@ck9=PP^TLnA}jZ;9x;j4)aqe(ik$CT4}2gw@^^pC?NgDWKsU+C5`Vbqlu-0H#|N!04~bJ`?Zosp)Dk; zy{<)3!1DEblPSJ{Iu1JzT}Dx+5HbI-<))My5=@40IE*GUg^8>leg-EOz`-+F}i4*UK zuWdv4MKGbeox18FRVO#n)&tc6-OoYNK1(Z1+}Xc>+3H9rDm1%m7JYPVQWa=x)gD6XSTLbQck#3ewoj7cMKE9v)8f4yIhrvk}H*tTrSR=#W+?<(`>afFe% z{3^F_r)Cp&CG3;iiFeZoQcQ2&z0j}vpU>Atah;uEswxgM&^aPony(;f#cxhvi_8O(QN8WzB2K{xf#bDZi>At|*h8V=J9lIV+Li97A# zyq+?{n#t~2pswr((kiWqZhS+ENSvG4t&oNjZ(!B-zKtyNJbH)%4+vzy;LDR?`#J+QML8=u-HzW%FA%W>ci3|jwtoI z0p0nkZ}#@rY;~z~z#R5p2}9CInf0vc*P)H;F;vv7~Eb(PY=Vu3}NQ9>zob z3vQGGBCcbLvKw0ttNmy+^$e!rY0jjLbliJmKqCj(@zs7dvwbs4IK|k}tp;C1?ugX7 zVFjb4UoqHLZj{Qri`meUfnUZk>DrWDWFm{XW{mhmBpVFG_t)P5tX=<0Z=I^MR!D(` zdkKj5CZ~qqvI?7L7|$Q!N*6zw#tRgr%YL}24Xit1!#YD*KG;5TY`Jp1GWF;n+)6GN z_@!4}$=6vp<+Ey0S(NmnFwJk()kmeD{U|cM8!>cYzhB}vQ=LtQe%wbW&JdWEvK!KE zXZj}=Tg%+>lP*D=p~V?`dUXPHG`qjxJGcKBz2NNk2sJ>JEQeK-0bo?tcR1vwa#e3( zuIpR}AN>7tH1t_E@)ek4%ttl1X|4W>(~{Ig2Z&bHZ+@EXheFims$3+Wf@mtnC}4TH zDNdh`rKU1u0s7aNaM;&rZ(ba=S|1T?%pz>T7M9v8q^Rjma2Plw1n z_U5PatWWIw6J}OE0D5Hg2#&m*oVa}tA;tRYZgsT_H+0!}{rq*v895R2wKp<&9fxc(K|b4?)H@e;5b)c1DG8HQ@S&d@T#|7epW1zR zgvaXv=|=IGci@frSXoBAMSyYF`MQPKYFfGN%n+LGzuSLhVzI4QZZ=okwA@vs>Mt(8 z??^BmuxUzae5sjdApy?)BD*7l@oGekkfu3PPvoJov0n?$_-AsV^x)gD?{tW!Kemj@C6CuD*zM;76ziTEmRkdP=^*?@ z;20{zZuSA<-L-_DrycOv$ANP6r!8e^ehx(%1ez&o-j3A)Y}go)ydfDwh6~D+7)q@G zN#^Ifu7%4#Mp4I)@S@iFY;6%}y_piz{jvW|*i?vLX(~zX znMsSs%j}9)0Gd=uZZzLcG};dXf=VBKJk{Uw6hZg? zCRtO4g{b1(eBt}`yX@&{~s#K zy9ruYwUeX>AX^C=P#uADyZN$v zS_~VHL833s3MX{pT<=d0O1+Y1dF#6Dhi`WO)knRZSzLn0W?{mQ=7o01xluW)df;2& zm4EZioj!@{Nz0kEk)W_V4f-)rv(F8xYUh-}3i_MuzD1O9pGZ-n&D~Ma-4dzi)*M{@ z<$v6x&vIwFoVe3?T*KpI+D#zqcyWEal=Az3f2-OLMNgO}pQ*^irCB9E9$2XrFL3+y z;5^h0f214QW4tigtL138+okFyD)sZnP)V8Y3xLS_hfiEbnGV~}qF7}o-)_zVI*=W3 zDk2@5iEml>4w`C~}$>RaQ7@0-jNk zj@$pPuHN<&HW4CN0e_^oF0<%5`Z zWp7VP=ER8-9y%iVmrf;59!M7NbHcJFxsRYGeh?C*-R;{lo{cxBwoHZv3GVQxQS|JB8mkfE1N8RH3_ zyH?{ar}`>`;&UJ9BWv-th`kPO1>5ytQ}R%sqlYg4&jM5O%Fc3T$F?dkZ}8ywPi+tL zgEzX~Mwi>7`_aBOcO7k$-23t>SHAVy$kx<$&BpVMtnzl86HP%;=`2f_+@?8{u0r8K z)zLYY?e0h^h2j~>VM-pIYhAht4aFsnvV(>4cPzFJR|h-c_98uMpAi&?J64<1`MJeX zL30rJoj!ahwlV1YRt?21`n9j_>^=`j;uTF<*|5hZQ@I+v^3B-Knp~ zL>cKPB7T5pmxjrH5Vgp)cQ9$(<0;)@=~!(PCJ}f#vxF&3eAS5H>+Srh8`P|+svWZVIaHshwt}mTe{e+-&*a8&`_+lsK%%V9H9yCZIz6n4taq0G#n2IJmFWe zz(*t>KiDHA*K>a5@Jbw*?7`=OXzLxid_;eA(5q`bI61tUA}Ps?@>#S;l6$&JHOY5k z?Il^k?nLmkbF5pZYm>W|FdCTPgMEyAqNT0fV*~Voin@wUohA0jp_vUj{WRI4=a)-= z&&65pr|HZkU;#gxJnmX>wPBi0isnUbZSHd0lgWTXs`e%Zu$myx49Aak_LhC8zkr62 z=t*bS=;wC|d)J9H=jvY$4C7&tvgOSuKRZ9Lhdc1@txcvWEB4V@*C@7k|Es<43~MU; z_6{gIgV^Z^I67+R29#cG$S5EL5C|;@LR6X*fza(pizr1pf`CXT(o0YXQCbv)&_OT+ z=^^yAdrll@=6UaZ-uM3B&%>v1_CD*Zy~=Ozv-aBSIDjd4ZNQ!&yPEaQP{_4$_3bQa z$bYiAyhO$KwnjtoufkYiAlteiadyWZhEW~YKgc#E%P4a>9>3;sgN2X2d}}9P^~Vez zjtT$B(#WJ4h%5T^M@lj%lNKvqM(!>G=UT2!yjsmD$j`5N*=DE%96iXr*+Q(iYiXDw`_r54A&TV!} z_Fz4k19Cr7BOoIlgG;g5A)-p$I>1`09t|X=_f0>TX+-MK`aCl>NJtAEa`E_6xGiRT zZi_PMZ9{}naVaSDn_J{s8%Ua*vp5o7u{?!3SU&fZoFhH^8=E~hvbXxGQ<}n3{L?J% zd-o3U#69EZ;TI4Pm*s!H!|c@QQ^}?69ZnY)db?pVbyfl5Z4B?ug6Z)GO8vToh>IyN z%0({gMeB**?0C0BPh2j?=Lp+Lx^{7EKG@D$cKwm}hIcr{~b=6Xe{S8n`v%1tORpJPuXy5jJ{-fAI zryUc|wkeAOV+%X(b;;cNfM%O9MC_(RI*Qhz3{+}^d`fkmLOe&6zl1R8U}E%$X~#HP z9cy~xlPB!H|5fK5FyP|bD4*RW1&X94Zqdly+BWCG8?82k=mB;9+z%$?Cs=ymt#4m% z>tBXRcwA1WEO(sbJh{Q3o#vYUd^gXE zr8jnv&G*6{3Utm46iq~|?bL$X?6D6quRGNAOEZTrYnu%=OLLc!5bJUJVP!6>K?yN# zpyABk{l}V~Y5Gw_uB&DaiN>Ow(p?lE>HvgR{-Bjxshw;yK2xOxE0EjFaN&?grNsC8 zZcn=o%^Z61ImFHg2J&*|MO?4%FX?X3OtNhp<%y;vA6N;~n7=*DQEf@{JeOR)QMxW5 z_FE}T;5wxKyz4LJvITDY9IA|Oh{HGVx!RFftNq~dh)>3I{Iw^E%p}BxPrMFm?y{Q= zAB@3!(ma9D$d9X@=Q4EhNRXu}>Zh~L}Pw?Bn8982lyxTpo*<7_S~u|s&zv=_WZ zClwWv8xE$nS3ZS`H$FoI{9@Z_5(NC8so&XkE9HKF5TvsH`4F+{<|EMTr!POJ6@FCe zF>Vs{K5L$`snW$^*}D0)X2G(J1JoWHL0;QM8_vkV91R!*uiBa&^astc7)iBoWW%v0QT- zA2_~G5n1DwgA#Q~?-%jTYJ16ur&1pP`eA}XCfN;r4xthokXKT36>Sa-0A8vj>wCdVROju7S=0QMQvtTv6sf z6^q4MscF6ASb=M;XOdIGsmGS-+B(GMB=UE#W6IUR8`rKmXY*9<5&HsOaJ>evmHmj7 zB_3`*EA*!u@|*%1ngmr83gThFm~HJH%&qTf6F5|T(h%tMcBPNTr97RkI$gOOpa?5g zkt&$qr#S>%wE_IF!@v}v5LP7JmSnj{lW1I;wDJ)P!kZgS1bk>^dH=rOm3KVZeQvI+ zEjRqVLIe&9p$mg2pTeg0YhdL&yhEM)1jSA`KW}|D7Rj-)GcWJ|tF7IW{G*s4)tKySbt9lF?gUo~k;_>*8m)xKjyi}FDe zV(g=Ay_BcD36C<|I8v|87(lpr?@Q5IF;E)RVgK;SOnP!E$}m0YQI=lK;et*E;6VTp zNG%SIHYeA#82bd4F#_S~^yRlAobjd~ZOg2q$#%_g583X%#g6izi2%n6Au9&XkMAIz z(V&|zDU%-0+D)iqh~y+E4z)iV9`{3|=k^T^&aH5mKYy@%(4eX2Q5LzyYoX*DBI!@u z$&B5PmgrK_DTu#(3fDjGcq!cp4S)^Ek@D}nIXUxXfv(<>oz@^Wnf8MZ`}8aPxb5@a zgt(ZKhYW89nOm0!H*!R3@7VeA*0JY?$CBGuKpz0G59r>MoBRxf;A_z2bASUbd!+v8 z>@D&fz+)kih@#^OP6{2jNDLPxj?KHe!_`L;QSfx_N7?%Lg~qaWkGVA$M?sLK$`??U zAf-k_X;)D}6yQ;19qn@~z^Pcs8KEaRO=Z9P$Gz;&I_^|4%k1P#f)JmIBy``mBv)u< zI07)P_)8oo2`h4P05G`&#-DOkc0Jk&=WGd%th6_}Co2nmqw!}S zfzl3h-jDB1Loa-oGpQ?cFpa0vWIOotfd0B5t zG{5je(OOB80gg zyIef9Je-Cy-YXdyi#DICkv0wINWatO8R}{}Tx*4C)NxBci=qm`9AvuK8N}dM5^Iep z&a{6Xyc)LPZel+4o4@Z~G!90iJ}IPh1%XHYGIvHO$`(IVQJ8Hcy`O!K@-(koyOx3h z#UGaLdprAQqHc}Bq7c_7W?Yw0j8;=*b-BNt7 z_Y2s6R*1wM<-*-s!IcvHc)Y*<`pv+tt!c0&(=p2+a^$XK;y9P#mZ;u0K-i4J&B)7O z+YNR2o8V&waz*03*GuGzX+rw(l&u$Fi+xWAI3pUkJ|i8}V6p zHBw1~396z=PzXyEoT||oTE?0N8@HdXnfmEqWKfNZ29i z_pL^W2szIwp52*aIgZb8+=h7}(*L?cY=Fh=N2v0)JYJUl&?mPKlyTVc1LTp@Xo*gm z0-7a5e72*Cc`3aY9rCm{qRD$VSnLw=_L0Z8&8z8`(@C@_g<$p$}yk_XN4`5^F z>6SZ8yG~l9rVD_a8tibQ-ko{MC2 z5jQMyOt&O_`(x>bI-5sLQ6WKVNrW{2wqr`#$}aCl zcS6#rGtcM%u$Z#-G<2dqTQt|NA%t$4kn`CA72mNlNvGqTbnM^=pP~79efhdKij>fxax4A? zorTP{u)EvFg4QdC8V4$e78K?*0_KeNsI7$0KH4)fSskRgWAig$#9`dV7r6T0?1&bI zO+;-YNS6D5Y0M7ot3t9#S-+%cI^C-H7AX=4f1&Hy$ICO8>r6H!UR_{Nc1l|yaivy! zt}vQD$2SJki^QvGT_yD%i*TI*KOBrS|F)+qZ)&oSgc18W(p(@*n%%r67TAQGFQbGb zU4hd;9#@hAg6_@j$bTXzBVn`dYbm54VI(hDBVmk|<| ze-kI`0p*7vyl3|FjQ4#BTpIsPX>AnsMQ3&BW|N2Bg4QiJeuU?Iy*Z+GC`$i{qX<!Hn% z3xFeJLiphj_qfK>1`hx^q3WrR{;`Gh%SGa zc;$$1uyg{e@?rwhUpL2ton}YP24RmUGn~8Bl)_w9kuxu?IHMg(O|1s+Vk}gj*2n#&=z=JU$A!J_RUm>czG_HrWtB5FD}3 zt)N2}vkS&l$1~4|Fu!6_7Xrcz69m{STHF4EI!mqJqzGE+vT`by(}T72X-Y3Y8C_h8ABqVkQ8b?Zli5e;qAQkN)>EY zmKUmPL!z{%uq(?Kr^|3+Ft5eL66=+s3b_EApTbg}MN4A2N;cY4kZ7XaQRHCg@Yxc* z-vmf6O}pG`3Im)C$!#t|W}~#x*pe6#zolI&vW?lj7@40kF;`tIu_@iCV75vuIpA0t z4Xm%2pp^>fw(LTaLAQX3fJo5Yyyo5>v>UueX!MBE37XK~<<4~XQ~Qf=>+kjK$7GDT zeC-^&QO%w4Kq$LVeddK!$p!Rk55Yo)rXCBJ8e(&qU9-@6e{WIrWJ@+CnA~09Q7CIo zz3!*+H>K5jOu6*sf+u>xOJwcfN~DI*hD3>h1~qQXhC!Ao5DT}IXV9G*fU5QwY4+@d zLPJ1DoDO5P`23*VNSVi2g=BKb%q&o{$eWxVUrgl3OYHnCo2uBXw%TT4Iw!ut|3hO<- zOsixaiXy@M{}?JLqb-4E>^=8o(jsH|vrfPihLXNIhqUqREGf3$SVfKn(ATA7K_~}t zV61ETvE;1rU7;$OQ3Z(;5{n5Q3c>TqV(tN~NLul0?Xf3$sjQCAge$h z*}6u-k`fPGh{uA4?{ErynfVcsQQS2{Pw`-zJ}w!{``d%cD9mk`l6(fU0b|z4N|U_m zaAG*Jq`uK>RH)Hrv&Sg7*z0}1l8m0ATCpN)>1e?~aKA+S$fJ2`-_wytjr&Ea+cPWX zCmTlj1}og$h8x}1uenUZ9%1|!hcF^e-!A15-)N7agx}L>hZ~x(}*Pf#sLp<|;{D$!V)G{0rG5IIVWL@Nf z@}2>m=s%EG=Qz)(9p%BX|-cFQ62e$ zkFB>>b0*AyT-`t2_yCW6L)Lt82CLqRuXSCaVXIYMeBFP+-!M&1Vv^~j3)BPDm*!{} z1XSxvNYrrYM$8Pb01EEYX!L9E4wMGLi#$8F`B@{Pb|X-8N=wGVqP@P_))TDmoiL+Z z8=B31vQcpT31b|nf=_LL%08Y`e!E3+l1aCrfW^IoXXWIs5Jf`m8fR)|4vba1Q9Zlq z5tKiO2iHx(Ddkf+4^UUEp&GR10^GF6wDR{wYP%OB_`DR`L4w3kcj(?J)2vrtrRbHx ze4{_t=reDamdxcdl-4ezgd!FtKwugtSahlys$_k&DgZ#$!I|niYa#I47i!y8Tc`&J zPq}Z?1j9TRTGk^euKd29X-#Wo`NzAeT!iEhc~Oxi1l-;_J_!@1cgbi&LC(lkmC@xj zlF7s(o}QQpA>j6V8or+ho}f4|7d$-Qc0?Ug# z=_eX3f{^%Ei&CIWZ+zrF8NZDlHHy2oKhfQkg%3S{sS0!M@MYYFRmbZm1dXMq&7>NZ zrC!7v=&nhj)j#S-tC0*9CbwjYh)>nBf-?-S8oZk8;S)f+2X$2s46bm#(s4ZU9_nNH zPJytLG6E>Hf!hEVV$;3 z=z&RJSo=u{1@{!*8ZRzke26BChX(9!&#%>mn5dB*u_Uw`%vzyq6F5BOtu8oGGwIu- z9b~l{dZ2}wEgM77aGiiJLLocn>h;N~M5I+iXxy{>xhO|jmRtlO2GA%LMv3JH)@xz8 zG7#))hu2LrNCIlJ$YaWUSlhWg9`#yWX|-ifXX&M!b8%L1zBm+5tHKPmJ!CU*Y%Q*y2>QuQoFtBcR{i()idh@hFe+t{se9t zd`4&&1KKe$OWnZJ^*#$8Ls}(UYhRqa(>e=PP>RX<2xqC(?y#37Mw6~CYF;Al=ebT@ z%r`awio&IgQSbaHRXijRMvZj$^d(No0$IgI9TK%8pHYk zg_)7M`h5m<)x+0wQ`iviDC>v0SN_@4LLSnC+fDT+lf;xzz)=+S2qHpWWs;f5)_cGa zy1NCWwJ`1N+e$|JR<2+etl&h#@M&q^i5kc6BU=(j-OTnKJDaU2+xF7yHv!#qRw1bo zUCRb7mX*TT$5|Jmk}kmZEM_oX&bZAA#N0%?Y5Hzi4a;ACzM@(7s?=s!7yhi+F)=|g z5vdQO+GV>+DPUquj`H@b*m$N?yNQnZ$M^(e{SEO+aQIRyU#Ql;IVsT7mawlvO{-H^h<@Ldq%Y`4^N=1!hc{uMTk{V#%L{@MopdC)VJXU)_6I{Y7C^kKY-7U6(4vgWxxVpVIx! zDrDI5*$ZV2Xd}*l&OgS#_>?pLoYii<~eur6l=lB~v3tt_n*+^2Bo^%ii<)887 z%bU#cmx$Zdq)M1j_7rcrSfbK-+l+(Hle?OrW+qe<8{Xm9kf?jIjTQ>zBZn`)IW;Tm zGNyW%HyawLZuPB2feJecmFbv!LK4H+rk{OLY$1ppYf}=nH`Qp?Pw)M zI>si*>cK5-a;;@?-GMPA(M6>O+^HDxfx_Rl+-sr?Q^ey`-iMpWHsoH^)zb{EU-aS< z%Q!o%JnB&fiCWH9bx~@_4Zxnv9)IEhV`??5PZTx@Ki|-d;IX%A6w$qozKA-6Tuq1} z@U^)r$!~MlNpPqf(?zF$q+R?fJS0u=ORt#c`L=st5p~4(5|?m>Q&ez+QypAB&(p&U z(t2PV8q}^7(t}Ei$ykPo5^Oh>q6y*U8t#c*BrmQP2@eyAW2XvEx?TUV5aP15WFhQ$ z>+^Z+%Y^?>3hX}6TNs>+*i5z&8~myah-ODbj)SJVw0L)U)5H`Pe&oSqVA;1SOGtu# zg^z8igIfGTigRzyIe9llV}p9qh8;gY-gEB5>QZ;^c6^c-e&7mxP4`;&4a!tNyG##L zRIVU;AOO#OQsACg?L~+BjJIOmTd86K56SBScqc}~=;~91`hH3QNe84txW*b}cx z<_0>SiNkf#0{ZScmD)Z<3bHiv3wWEV?d%$|)H}5eQ%X-sLwaCVjiITN_{ldxQMn>{ zWJh&5gb~3OjeWYo*Avq~o`j|i0dqm`0IV%?no?@FSHrXR{ejC&^gA$r zBa1)WL`uMz?DgB_yQiL?IA)8JZ@{gH&S-_iMNtR4$Fk2vCb@sF5JZM1CPkBo~g>I$t^0@y-ds2Kz!kAQ<5X{0D)y1a-c7bYr_Lw+@_>O z3^P<))UbxMs>N%Qpf|EVMV7@(s>M{9^{H(6WvV_`Efx~VxFcJGA)Z&}6IbH};l{6=UVpC*f*)$3k@Te# z`YeFY2t0td;ALygZpER5@gz|#Bdc>ZcD4Pn+@&@l0;sbW2a+O)h0I#F{Oz(r;-2&_ z!7%c`V`~4+Up?wEP?STZWyfuU_ERcmpK_d902{gd@}i@~V*kK79(%3(03K-Bd#A5^ zy0)sS3|_7JoU87Mds}80R|bC|kl0KBG%r|^hhW&*^uXfBR~IA+XEyKPHwAg@lLy4G zOA`U{c*I@!;Z{#!f<1LCQ?)hwtv+dY)W8GJDE>%l01*+SfOpika|uXy;K~PAu$k8U zVfdUT_;$heG=Nk1uv3nqifFtm+19(g9fIAVuN#S7ok(O)+?klsa*lh5Cm@hq!d20; zQ@Tm95?D)zSLA1_#d<>Pa_(l`N!%ypPRl3WhCgUI{wq1B_ht`fC8V9l9++YRvq!um zNg6I@kX#WfYWi$+u(=ht&Ef_WSF%z&>o$D=Sx{x8Zc8gVN-~*q*6noGSafRF1DD8W zdW1j@5!cWt6aaCaS%7Vx!A}%gKFbl&A-gp6%49^4Hf4z6k%T|$9_A)FZd)C9UecY_h>Ew{VQ$7Cm#xwTO6_vaBLaShTgc)z z<_`nx2@27GLvjIEGl2-U=R=FLnj(^X;1Jz+FNz5k9y^I%zs1?P44+6iaS_e$WE|%~ z?T5Py_*F+E0`pHrl|vJh@=UPR@Q;spo6N$Ub~~r#%*Fxld*@31-J1OuqLa9U7ggj^ z3N89{V+DDf>&Ku>2ZM>Vo2@pYG=I0U2Z{|C;XF%Gk@Y9^)_I5F2^N0Da#kSh=9*pH z8_%)o;2^nCQvg0pI)(4AR8t39t8+w>@q`#h*~`5^J@2 z=Zj!i8Wa+#e-~>wy#1AbTD3!#y+Iw-_d1~u`oQGoivuMJ5#`JBjqr;lo=aZv4c$9-6OgoYGhHcG_j zJN-Ls!W-%_y#Jw{C&CkWRq5oFTc|%&a+G&uIfjtGJxU}ACP>wuV15}IQMGfC_UxIw zm^AR3KV}w4qKETj{&@F=C|`1TWF@N~{VZQ8KrHd3o%N>G1N^Bi;0u7S zMjt7_oMFCHgn&x$YZWo9C4j`bf4;t1DZ^Y!NAK{ZXq9zT2Q{?((eX>6-|ps^@FRW6t~+uO%oSxEcRyBsRM~iu|t3KvhnK4ZI+@esOFqd@mdspdl~w z589q`_5F9Nd+q%ALvP;4RPV9x7%uR$eHU{a*dgvj2Hb{E1{s61KM?Q(qL8Fx%6Fbb zyG1Ll;*Eqm(~;{uyQiBEH9uyUiTH+r))PmG$@x z3TQar6)f4ThSi1p`Bb_89UySO%e`czvszPIr<>ukRXDSr%`dyqCVtEw<(Fyx_u~%= zgyGW)0qM$Hm5*}Y2EsaTQbuH}SL@<|AXU#-?voNSCU&>xssVn1*dq|Emxn?;EQ9n` zmGgpJeVdOpA#2xiF9YGa9AZ}T*Q29#T`|N}-ll$j-~)Nblpn)6Px@sa93lGfVfy8 zOUs$Ti2~W}yd9hp_G(!{S@<8aG?#}sVlma7Kp5brKX^#gwxJ8CHqV#3_O<7Y{yhK& ze|Hz#1R{#+e+CA_xo^7&hLr(ndNkmzSVb1nBj4mXk9+Y|NGzb=W^lrs*sS~Lt zatxD~{vBTq79fqPo(vI~Z5Y2cg0lr~^v1gVPmAQq{E4~T7x+(i1^#6jUsM-=_~nr^ z7vPiie&E={{_&gRCYh>#Dd4Y=)qg|S=AP;0IeBCOU_>rcpno_gCci@q8#y zju0qzlM?JK_Y{oPI+X;k(KU4505-vi>ub2SQBNTK`&+VufF3|Rb2ESy8@>MbmW9$5 zpXl(~EMO4R-Y(rO2j-t&Nm;GCtFhPW>t7ou;tqH^M6 zTlM+l^~0g#O&*jCQxBcopAS_rUmRIKL@iox^!T#qjtIaIOMfjN?^`tYl>icP#kba& zS&~LTXU>jF3cilMvgtn-!ZepW$to4f4qHa}1{d3u`p++EjnRTK=KX?BvH!^3_zWzB zeryZX;7xkIGq=`5T}v9)nVmq4$)sUV8u3m$7^Id))(5d>>ni72z^zp{1xfbDklz6d z17QN<`|vv$8E4IJxw4nxrZGryY)@9pL-cH0{G)iJ(F+7Cw>f1t@_@v}*{& ztrYt;$SAMBNJ2f8Jo|IRok!Zx2$nk}et4-!|{pPfgtPZ+MCs$a&#NBT<*W7G!cD!$heEz-9bFd*CU)KTW0J&u zhpAM%h5(%*7P?p}E-3MO1tR#MA_@FNW#+?INH5*%J+#756ZIUw^!?~oR7Kit6j@D! zsEZbyLOequ0@I;6*=%1e+uE@=?;S6S)MPR@r<^OifYMLZYb0P(=-foyZQbm6So|L%@|d;Kece literal 0 HcmV?d00001 From 2df5f72e4d4d72363a9ee236712db589f859fc55 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Sat, 14 Nov 2015 14:36:05 -0800 Subject: [PATCH 08/33] Improves readability --- examples/wordpress/wordpress-resources.yaml | 1 + examples/wordpress/wordpress.jinja | 2 ++ 2 files changed, 3 insertions(+) diff --git a/examples/wordpress/wordpress-resources.yaml b/examples/wordpress/wordpress-resources.yaml index b66bfec32..00f709de0 100644 --- a/examples/wordpress/wordpress-resources.yaml +++ b/examples/wordpress/wordpress-resources.yaml @@ -1,3 +1,4 @@ +# Google Cloud Deployment Manager template resources: - name: nfs-disk type: compute.v1.disk diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index 35c6a159d..ccc9646eb 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -16,8 +16,10 @@ {% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} + imports: - path: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + resources: - name: nfs-server type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py From eed0ddaa9661a2660e46566aa541b3ea4ce47ac6 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 15:16:53 -0800 Subject: [PATCH 09/33] Updates Wordpress README for Kubernetes 1.1 release --- examples/wordpress/README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 2f40c58df..1c87b686a 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -1,10 +1,7 @@ # Wordpress Example - Welcome to the Wordpress example. It shows you how to deploy a Wordpress application using Deployment Manager. ## Prerequisites - - ### Deployment Manager First, make sure DM is installed in your Kubernetes cluster by following the instructions in the top level [README.md](../../README.md). @@ -30,17 +27,12 @@ resources: ``` ### Privileged containers -To use NFS we need to be able to launch privileged containers. If your Kubernetes cluster doesn't support this you can either manually change this in every Kubernetes minion or you could set the flag at `kubernetes/saltbase/pillar/privilege.sls` to true and (re)launch your Kubernetes cluster. Once Kubernetes 1.1 releases this should be enabled by default. +To use NFS we need to be able to launch privileged containers. Since the release of Kubernetes 1.1 privileged container support is enabled by default. If your Kubernetes cluster doesn't support privileged containers you need to manually change this by setting the flag at `kubernetes/saltbase/pillar/privilege.sls` to true. ### NFS Library -Currently the nfs-common library should be installed by default on Kubernetes nodes. In case nfs-common is not installed you can install it on all Kubernetes nodes using the following command: -``` -gcloud compute instances list | cut -d ' ' -f 1 | tail -n +2 | xargs -n1 gcloud compute ssh --command="sudo apt-get update;sudo apt-get -y install nfs-common" -``` - +Mounting NFS volumes requires NFS libraries. Since the release of Kubernetes 1.1 the NFS libraries are installed by default. If they are not installed on your Kubernetes cluster you need to install them manually. ## Understanding the Wordpress example template - Let's take a closer look at the template used by the Wordpress example. The Wordpress application consists of 4 microservices: an nginx service, a wordpress-php service, a MySQL service, and an NFS service. The architecture looks as follows: ![Architecture](architecture.png) From 8a51089fd0d5ab9af39aec9b7083713a2302dc9c Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 15:53:15 -0800 Subject: [PATCH 10/33] Adds extra primitive types for the Wordpress example --- manager/manager/types.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manager/manager/types.go b/manager/manager/types.go index 97af0697b..6603845d9 100644 --- a/manager/manager/types.go +++ b/manager/manager/types.go @@ -25,6 +25,9 @@ var Primitives = map[string]bool{ "Service": true, "Namespace": true, "Volume": true, + "Endpoints": true, + "PersistentVolumeClaim": true, + "PersistentVolume": true, } // SchemaImport represents an import as declared in a schema file. From 1e8ac2d7a8ca87df60ec6d92879e7a0fb9c8b6d4 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 16:01:08 -0800 Subject: [PATCH 11/33] Move types/* to templates/*" --- {types => templates}/replicatedservice/v2/replicatedservice.py | 0 .../replicatedservice/v2/replicatedservice.py.schema | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {types => templates}/replicatedservice/v2/replicatedservice.py (100%) rename {types => templates}/replicatedservice/v2/replicatedservice.py.schema (100%) diff --git a/types/replicatedservice/v2/replicatedservice.py b/templates/replicatedservice/v2/replicatedservice.py similarity index 100% rename from types/replicatedservice/v2/replicatedservice.py rename to templates/replicatedservice/v2/replicatedservice.py diff --git a/types/replicatedservice/v2/replicatedservice.py.schema b/templates/replicatedservice/v2/replicatedservice.py.schema similarity index 100% rename from types/replicatedservice/v2/replicatedservice.py.schema rename to templates/replicatedservice/v2/replicatedservice.py.schema From 77312eeb0f4153e49384f100b9be97f362291ea7 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 16:48:14 -0800 Subject: [PATCH 12/33] Updates README to link to master/templates --- examples/wordpress/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 1c87b686a..b13173913 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -62,7 +62,7 @@ The nginx service is a replicated service with 2 replicas: ``` - name: nginx - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -83,7 +83,7 @@ The wordpress-php service is a replicated service with 2 replicas: ``` - name: wordpress-php - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} @@ -107,7 +107,7 @@ The MySQL service is a replicated service with a single replica: ``` - name: mysql - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ MYSQL_PORT }} container_port: {{ MYSQL_PORT }} @@ -128,7 +128,7 @@ The NFS service is a replicated service with a single replica: ``` - name: nfs-server - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NFS_SERVER_PORT }} container_port: {{ NFS_SERVER_PORT }} From 65b3c7e4ff43527c3e9817fd63b187eb107df14d Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 16:49:56 -0800 Subject: [PATCH 13/33] Changes the Wordpress template so that it uses PersistentVolumeClaims --- examples/wordpress/wordpress.jinja | 50 +++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index ccc9646eb..45cdcb89f 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -18,16 +18,16 @@ {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} imports: -- path: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +- path: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py resources: - name: nfs-server - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NFS_SERVER_PORT }} container_port: {{ NFS_SERVER_PORT }} replicas: 1 # Has to be 1 because of the persistent disk - image: jsafrane/nfs-data + image: gcr.io/google_containers/volume-nfs privileged: true cluster_ip: {{ NFS_SERVER_IP }} volumes: @@ -35,8 +35,36 @@ resources: gcePersistentDisk: pdName: {{ NFS_SERVER_DISK }} fsType: {{ NFS_SERVER_DISK_FSTYPE }} +- name: nfs-pvc + type: PersistentVolumeClaim + properties: + kind: PersistentVolumeClaim + apiVersion: v1 + metadata: + name: nfs + spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Mi +- name: nfs-pv + type: PersistentVolume + properties: + apiVersion: v1 + kind: PersistentVolume + metadata: + name: nfs + spec: + capacity: + storage: 1Mi + accessModes: + - ReadWriteMany + nfs: + server: {{ NFS_SERVER_IP }} + path: "/" - name: nginx - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -45,11 +73,10 @@ resources: image: gcr.io/{{ PROJECT }}/nginx:latest volumes: - mount_path: /var/www/html - nfs: - server: {{ NFS_SERVER_IP }} - path: / + persistentVolumeClaim: + claimName: nfs - name: mysql - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ MYSQL_PORT }} container_port: {{ MYSQL_PORT }} @@ -64,7 +91,7 @@ resources: pdName: {{ MYSQL_DISK }} fsType: {{ MYSQL_DISK_FSTYPE }} - name: wordpress-php - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} @@ -78,6 +105,5 @@ resources: value: mysql-service volumes: - mount_path: /var/www/html - nfs: - server: {{ NFS_SERVER_IP }} - path: / + persistentVolumeClaim: + claimName: nfs From 3ed2616f43bc3a3905a97b5922b86ea8b67aa2fe Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 16:59:26 -0800 Subject: [PATCH 14/33] Creates an NFS type --- examples/wordpress/wordpress.jinja | 50 ++++-------------------------- templates/nfs/v1/nfs.jinja | 49 +++++++++++++++++++++++++++++ templates/nfs/v1/nfs.schema | 1 + templates/nfs/v1/nfs.yaml | 2 ++ 4 files changed, 58 insertions(+), 44 deletions(-) create mode 100644 templates/nfs/v1/nfs.jinja create mode 100644 templates/nfs/v1/nfs.schema create mode 100644 templates/nfs/v1/nfs.yaml diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index 45cdcb89f..4c409e02b 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -17,52 +17,14 @@ {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} -imports: -- path: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py - resources: -- name: nfs-server - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py - properties: - service_port: {{ NFS_SERVER_PORT }} - container_port: {{ NFS_SERVER_PORT }} - replicas: 1 # Has to be 1 because of the persistent disk - image: gcr.io/google_containers/volume-nfs - privileged: true - cluster_ip: {{ NFS_SERVER_IP }} - volumes: - - mount_path: /mnt/data - gcePersistentDisk: - pdName: {{ NFS_SERVER_DISK }} - fsType: {{ NFS_SERVER_DISK_FSTYPE }} -- name: nfs-pvc - type: PersistentVolumeClaim - properties: - kind: PersistentVolumeClaim - apiVersion: v1 - metadata: - name: nfs - spec: - accessModes: - - ReadWriteMany - resources: - requests: - storage: 1Mi -- name: nfs-pv - type: PersistentVolume +- name: nfs + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja properties: - apiVersion: v1 - kind: PersistentVolume - metadata: - name: nfs - spec: - capacity: - storage: 1Mi - accessModes: - - ReadWriteMany - nfs: - server: {{ NFS_SERVER_IP }} - path: "/" + ip: {{ NFS_SERVER_IP }} + port: {{ NFS_SERVER_PORT }} + disk: {{ NFS_SERVER_DISK }} + fstype: {{NFS_SERVER_DISK_FSTYPE }} - name: nginx type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: diff --git a/templates/nfs/v1/nfs.jinja b/templates/nfs/v1/nfs.jinja new file mode 100644 index 000000000..11ee661a2 --- /dev/null +++ b/templates/nfs/v1/nfs.jinja @@ -0,0 +1,49 @@ +{% set PROPERTIES = properties or {} %} +{% set SERVER_IP = PROPERTIES['ip'] or '10.0.253.247' %} +{% set SERVER_PORT = PROPERTIES['port'] or 2049 %} +{% set SERVER_DISK = PROPERTIES['disk'] or 'nfs-disk' %} +{% set SERVER_DISK_FSTYPE = PROPERTIES['fstype'] or 'ext4' %} + +resources: +- name: nfs-server + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ SERVER_PORT }} + container_port: {{ SERVER_PORT }} + replicas: 1 # Has to be 1 because of the persistent disk + image: gcr.io/google_containers/volume-nfs + privileged: true + cluster_ip: {{ SERVER_IP }} + volumes: + - mount_path: /mnt/data + gcePersistentDisk: + pdName: {{ SERVER_DISK }} + fsType: {{ SERVER_DISK_FSTYPE }} +- name: nfs-pvc + type: PersistentVolumeClaim + properties: + kind: PersistentVolumeClaim + apiVersion: v1 + metadata: + name: nfs + spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Mi +- name: nfs-pv + type: PersistentVolume + properties: + apiVersion: v1 + kind: PersistentVolume + metadata: + name: nfs + spec: + capacity: + storage: 1Mi + accessModes: + - ReadWriteMany + nfs: + server: {{ SERVER_IP }} + path: "/" diff --git a/templates/nfs/v1/nfs.schema b/templates/nfs/v1/nfs.schema new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/templates/nfs/v1/nfs.schema @@ -0,0 +1 @@ + diff --git a/templates/nfs/v1/nfs.yaml b/templates/nfs/v1/nfs.yaml new file mode 100644 index 000000000..4d5c68d8a --- /dev/null +++ b/templates/nfs/v1/nfs.yaml @@ -0,0 +1,2 @@ +- name: nfs + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja From 7704aeec1fe44f60d88f7c1beccd3c71bed811be Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 15:36:03 -0800 Subject: [PATCH 15/33] Adds Wordpress example Updates Wordpress example Change hardcoded type urls from leendersr/deployment-manager to kubernetes/deployment-manager Fix typo, we want to deploy wordpress.yaml not .jinja Fix spelling Updates Makefile to use generic Docker registry Adds picture to README Improves readability Updates Wordpress README for Kubernetes 1.1 release Adds extra primitive types for the Wordpress example Move types/* to templates/* Updates README to link to master/templates Changes the Wordpress template so that it uses PersistentVolumeClaims Creates an NFS type --- examples/wordpress/README.md | 152 ++++++++++++++ examples/wordpress/architecture.png | Bin 0 -> 33703 bytes examples/wordpress/images/nginx/Dockerfile | 2 + examples/wordpress/images/nginx/Makefile | 23 +++ examples/wordpress/images/nginx/default.conf | 48 +++++ examples/wordpress/wordpress-resources.yaml | 12 ++ examples/wordpress/wordpress.jinja | 71 +++++++ examples/wordpress/wordpress.jinja.schema | 69 +++++++ examples/wordpress/wordpress.yaml | 6 + manager/manager/types.go | 3 + templates/nfs/v1/nfs.jinja | 49 +++++ templates/nfs/v1/nfs.schema | 1 + templates/nfs/v1/nfs.yaml | 2 + .../replicatedservice/v2/replicatedservice.py | 194 ++++++++++++++++++ .../v2/replicatedservice.py.schema | 91 ++++++++ 15 files changed, 723 insertions(+) create mode 100644 examples/wordpress/README.md create mode 100644 examples/wordpress/architecture.png create mode 100644 examples/wordpress/images/nginx/Dockerfile create mode 100644 examples/wordpress/images/nginx/Makefile create mode 100644 examples/wordpress/images/nginx/default.conf create mode 100644 examples/wordpress/wordpress-resources.yaml create mode 100644 examples/wordpress/wordpress.jinja create mode 100644 examples/wordpress/wordpress.jinja.schema create mode 100644 examples/wordpress/wordpress.yaml create mode 100644 templates/nfs/v1/nfs.jinja create mode 100644 templates/nfs/v1/nfs.schema create mode 100644 templates/nfs/v1/nfs.yaml create mode 100644 templates/replicatedservice/v2/replicatedservice.py create mode 100644 templates/replicatedservice/v2/replicatedservice.py.schema diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md new file mode 100644 index 000000000..b13173913 --- /dev/null +++ b/examples/wordpress/README.md @@ -0,0 +1,152 @@ +# Wordpress Example +Welcome to the Wordpress example. It shows you how to deploy a Wordpress application using Deployment Manager. + +## Prerequisites +### Deployment Manager +First, make sure DM is installed in your Kubernetes cluster by following the instructions in the top level +[README.md](../../README.md). + +### Google Cloud Resources +The Wordpress application will make use of several persistent disks, which we will host on Google Cloud. To create these disks we will create a deployment using Google Cloud Deployment Manager: +```gcloud deployment-manager deployments create wordpress-resources --config wordpress-resources.yaml``` + +where `wordpress-resources.yaml` looks as follows: + +``` +resources: +- name: nfs-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +- name: mysql-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +``` + +### Privileged containers +To use NFS we need to be able to launch privileged containers. Since the release of Kubernetes 1.1 privileged container support is enabled by default. If your Kubernetes cluster doesn't support privileged containers you need to manually change this by setting the flag at `kubernetes/saltbase/pillar/privilege.sls` to true. + +### NFS Library +Mounting NFS volumes requires NFS libraries. Since the release of Kubernetes 1.1 the NFS libraries are installed by default. If they are not installed on your Kubernetes cluster you need to install them manually. + +## Understanding the Wordpress example template +Let's take a closer look at the template used by the Wordpress example. The Wordpress application consists of 4 microservices: an nginx service, a wordpress-php service, a MySQL service, and an NFS service. The architecture looks as follows: + +![Architecture](architecture.png) + +### Variables +The template contains the following variables: + +``` +{% set PROPERTIES = properties or {} %} +{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %} +{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} +{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} +{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} +{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} +{% set NGINX = PROPERTIES['nginx'] or {} %} +{% set NGINX_PORT = 80 %} +{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} +{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %} +{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} +{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} +{% set MYSQL = PROPERTIES['mysql'] or {} %} {% set MYSQL_PORT = MYSQL['port'] or 3306 %} {% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +``` + +### Nginx service +The nginx service is a replicated service with 2 replicas: + +``` +- name: nginx + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NGINX_PORT }} + container_port: {{ NGINX_PORT }} + replicas: {{ NGINX_REPLICAS }} + external_service: true + image: gcr.io/{{ PROJECT }}/nginx:latest + volumes: + - mount_path: /var/www/html + nfs: + server: {{ NFS_SERVER_IP }} + path: / +``` + +The nginx image builds upon the standard nginx image and simply copies a custom configuration file. + +### Wordpress-php service +The wordpress-php service is a replicated service with 2 replicas: + +``` +- name: wordpress-php + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_name: wordpress-php + service_port: {{ WORDPRESS_PHP_PORT }} + container_port: {{ WORDPRESS_PHP_PORT }} + replicas: 2 + image: wordpress:fpm + env: + - name: WORDPRESS_DB_PASSWORD + value: {{ MYSQL_PASSWORD }} + - name: WORDPRESS_DB_HOST + value: mysql-service + volumes: + - mount_path: /var/www/html + nfs: + server: {{ NFS_SERVER_IP }} + path: / +``` + +### MySQL service +The MySQL service is a replicated service with a single replica: + +``` +- name: mysql + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ MYSQL_PORT }} + container_port: {{ MYSQL_PORT }} + replicas: 1 + image: mysql:5.6 + env: + - name: MYSQL_ROOT_PASSWORD + value: {{ MYSQL_PASSWORD }} + volumes: + - mount_path: /var/lib/mysql + gcePersistentDisk: + pdName: {{ MYSQL_DISK }} + fsType: {{ MYSQL_DISK_FSTYPE }} +``` + +### NFS service +The NFS service is a replicated service with a single replica: + +``` +- name: nfs-server + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NFS_SERVER_PORT }} + container_port: {{ NFS_SERVER_PORT }} + replicas: 1 # Has to be 1 because of the persistent disk + image: jsafrane/nfs-data + privileged: true + cluster_ip: {{ NFS_SERVER_IP }} + volumes: + - mount_path: /mnt/data + gcePersistentDisk: + pdName: {{ NFS_SERVER_DISK }} + fsType: {{ NFS_SERVER_DISK_FSTYPE }} +``` + +## Deploying Wordpress +We can now deploy Wordpress using: + +``` +dm deploy examples/wordpress/wordpress.yaml +``` + diff --git a/examples/wordpress/architecture.png b/examples/wordpress/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..853039e63312eaeebcbbe8cdb1c24f7c59c3f88e GIT binary patch literal 33703 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 literal 0 HcmV?d00001 diff --git a/examples/wordpress/images/nginx/Dockerfile b/examples/wordpress/images/nginx/Dockerfile new file mode 100644 index 000000000..ce14e868d --- /dev/null +++ b/examples/wordpress/images/nginx/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx +COPY default.conf /etc/nginx/conf.d/default.conf diff --git a/examples/wordpress/images/nginx/Makefile b/examples/wordpress/images/nginx/Makefile new file mode 100644 index 000000000..3974d978e --- /dev/null +++ b/examples/wordpress/images/nginx/Makefile @@ -0,0 +1,23 @@ +.PHONY: all build push clean + +DOCKER_REGISTRY = gcr.io +PREFIX = $(DOCKER_REGISTRY)/$(PROJECT) +IMAGE = nginx +TAG = latest + +DIR = . + +all: build + +build: + docker build -t $(PREFIX)/$(IMAGE):$(TAG) $(DIR) + +push: build +ifeq ($(DOCKER_REGISTRY),gcr.io) + gcloud docker push $(PREFIX)/$(IMAGE):$(TAG) +else + docker push $(PREFIX)/$(IMAGE):$(TAG) +endif + +clean: + docker rmi $(PREFIX)/$(IMAGE):$(TAG) diff --git a/examples/wordpress/images/nginx/default.conf b/examples/wordpress/images/nginx/default.conf new file mode 100644 index 000000000..af2d7ebf8 --- /dev/null +++ b/examples/wordpress/images/nginx/default.conf @@ -0,0 +1,48 @@ +upstream phpcgi { + server wordpress-php:9000; +} + +server { + listen 80 ; + + root /var/www/html; + index index.php index.html index.htm; + + server_name localhost; + + location / { + try_files $uri $uri/ =404; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + + # With php5-cgi alone: + fastcgi_pass phpcgi; + + # With php5-fpm: + fastcgi_index index.php; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param DOCUMENT_URI $document_uri; + fastcgi_param DOCUMENT_ROOT $document_root; + fastcgi_param SERVER_PROTOCOL $server_protocol; + + fastcgi_param GATEWAY_INTERFACE CGI/1.1; + fastcgi_param SERVER_SOFTWARE nginx; + + fastcgi_param REMOTE_ADDR $remote_addr; + fastcgi_param REMOTE_PORT $remote_port; + fastcgi_param SERVER_ADDR $server_addr; + fastcgi_param SERVER_PORT $server_port; + fastcgi_param SERVER_NAME $server_name; + #include fastcgi_params; + } +} diff --git a/examples/wordpress/wordpress-resources.yaml b/examples/wordpress/wordpress-resources.yaml new file mode 100644 index 000000000..00f709de0 --- /dev/null +++ b/examples/wordpress/wordpress-resources.yaml @@ -0,0 +1,12 @@ +# Google Cloud Deployment Manager template +resources: +- name: nfs-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +- name: mysql-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja new file mode 100644 index 000000000..4c409e02b --- /dev/null +++ b/examples/wordpress/wordpress.jinja @@ -0,0 +1,71 @@ +{% set PROPERTIES = properties or {} %} +{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %} +{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} +{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} +{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} +{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} +{% set NGINX = PROPERTIES['nginx'] or {} %} +{% set NGINX_PORT = 80 %} +{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} +{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %} +{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} +{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} +{% set MYSQL = PROPERTIES['mysql'] or {} %} +{% set MYSQL_PORT = MYSQL['port'] or 3306 %} +{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} +{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} +{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} + +resources: +- name: nfs + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja + properties: + ip: {{ NFS_SERVER_IP }} + port: {{ NFS_SERVER_PORT }} + disk: {{ NFS_SERVER_DISK }} + fstype: {{NFS_SERVER_DISK_FSTYPE }} +- name: nginx + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NGINX_PORT }} + container_port: {{ NGINX_PORT }} + replicas: {{ NGINX_REPLICAS }} + external_service: true + image: gcr.io/{{ PROJECT }}/nginx:latest + volumes: + - mount_path: /var/www/html + persistentVolumeClaim: + claimName: nfs +- name: mysql + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ MYSQL_PORT }} + container_port: {{ MYSQL_PORT }} + replicas: 1 + image: mysql:5.6 + env: + - name: MYSQL_ROOT_PASSWORD + value: {{ MYSQL_PASSWORD }} + volumes: + - mount_path: /var/lib/mysql + gcePersistentDisk: + pdName: {{ MYSQL_DISK }} + fsType: {{ MYSQL_DISK_FSTYPE }} +- name: wordpress-php + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_name: wordpress-php + service_port: {{ WORDPRESS_PHP_PORT }} + container_port: {{ WORDPRESS_PHP_PORT }} + replicas: 2 + image: wordpress:fpm + env: + - name: WORDPRESS_DB_PASSWORD + value: {{ MYSQL_PASSWORD }} + - name: WORDPRESS_DB_HOST + value: mysql-service + volumes: + - mount_path: /var/www/html + persistentVolumeClaim: + claimName: nfs diff --git a/examples/wordpress/wordpress.jinja.schema b/examples/wordpress/wordpress.jinja.schema new file mode 100644 index 000000000..215b47e1e --- /dev/null +++ b/examples/wordpress/wordpress.jinja.schema @@ -0,0 +1,69 @@ +info: + title: Wordpress + description: | + Defines a Wordpress website by defining four replicated services: an NFS service, an nginx service, a wordpress-php service, and a MySQL service. + + The nginx service and the Wordpress-php service both use NFS to share files. + +properties: + project: + type: string + default: dm-k8s-testing + description: Project location to load the images from. + nfs-service: + type: object + properties: + ip: + type: string + default: 10.0.253.247 + description: The IP of the NFS service. + port: + type: int + default: 2049 + description: The port of the NFS service. + disk: + type: string + default: nfs-disk + description: The name of the persistent disk the NFS service uses. + fstype: + type: string + default: ext4 + description: The filesystem the disk of the NFS service uses. + nginx: + type: object + properties: + replicas: + type: int + default: 2 + description: The number of replicas for the nginx service. + wordpress-php: + type: object + properties: + replicas: + type: int + default: 2 + description: The number of replicas for the wordpress-php service. + port: + type: int + default: 9000 + description: The port the wordpress-php service runs on. + mysql: + type: object + properties: + port: + type: int + default: 3306 + description: The port the MySQL service runs on. + password: + type: string + default: mysql-password + description: The root password of the MySQL service. + disk: + type: string + default: mysql-disk + description: The name of the persistent disk the MySQL service uses. + fstype: + type: string + default: ext4 + description: The filesystem the disk of the MySQL service uses. + diff --git a/examples/wordpress/wordpress.yaml b/examples/wordpress/wordpress.yaml new file mode 100644 index 000000000..b401897ab --- /dev/null +++ b/examples/wordpress/wordpress.yaml @@ -0,0 +1,6 @@ +imports: +- path: wordpress.jinja + +resources: +- name: wordpress + type: wordpress.jinja diff --git a/manager/manager/types.go b/manager/manager/types.go index e9c95e728..8301a5bdb 100644 --- a/manager/manager/types.go +++ b/manager/manager/types.go @@ -25,6 +25,9 @@ var Primitives = map[string]bool{ "Service": true, "Namespace": true, "Volume": true, + "Endpoints": true, + "PersistentVolumeClaim": true, + "PersistentVolume": true, } // SchemaImport represents an import as declared in a schema file. diff --git a/templates/nfs/v1/nfs.jinja b/templates/nfs/v1/nfs.jinja new file mode 100644 index 000000000..11ee661a2 --- /dev/null +++ b/templates/nfs/v1/nfs.jinja @@ -0,0 +1,49 @@ +{% set PROPERTIES = properties or {} %} +{% set SERVER_IP = PROPERTIES['ip'] or '10.0.253.247' %} +{% set SERVER_PORT = PROPERTIES['port'] or 2049 %} +{% set SERVER_DISK = PROPERTIES['disk'] or 'nfs-disk' %} +{% set SERVER_DISK_FSTYPE = PROPERTIES['fstype'] or 'ext4' %} + +resources: +- name: nfs-server + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ SERVER_PORT }} + container_port: {{ SERVER_PORT }} + replicas: 1 # Has to be 1 because of the persistent disk + image: gcr.io/google_containers/volume-nfs + privileged: true + cluster_ip: {{ SERVER_IP }} + volumes: + - mount_path: /mnt/data + gcePersistentDisk: + pdName: {{ SERVER_DISK }} + fsType: {{ SERVER_DISK_FSTYPE }} +- name: nfs-pvc + type: PersistentVolumeClaim + properties: + kind: PersistentVolumeClaim + apiVersion: v1 + metadata: + name: nfs + spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Mi +- name: nfs-pv + type: PersistentVolume + properties: + apiVersion: v1 + kind: PersistentVolume + metadata: + name: nfs + spec: + capacity: + storage: 1Mi + accessModes: + - ReadWriteMany + nfs: + server: {{ SERVER_IP }} + path: "/" diff --git a/templates/nfs/v1/nfs.schema b/templates/nfs/v1/nfs.schema new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/templates/nfs/v1/nfs.schema @@ -0,0 +1 @@ + diff --git a/templates/nfs/v1/nfs.yaml b/templates/nfs/v1/nfs.yaml new file mode 100644 index 000000000..4d5c68d8a --- /dev/null +++ b/templates/nfs/v1/nfs.yaml @@ -0,0 +1,2 @@ +- name: nfs + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja diff --git a/templates/replicatedservice/v2/replicatedservice.py b/templates/replicatedservice/v2/replicatedservice.py new file mode 100644 index 000000000..a6ef0d71e --- /dev/null +++ b/templates/replicatedservice/v2/replicatedservice.py @@ -0,0 +1,194 @@ +"""Defines a ReplicatedService type by creating both a Service and an RC. + +This module creates a typical abstraction for running a service in a +Kubernetes cluster, namely a replication controller and a service packaged +together into a single unit. +""" + +import yaml + +SERVICE_TYPE_COLLECTION = 'Service' +RC_TYPE_COLLECTION = 'ReplicationController' + + +def GenerateConfig(context): + """Generates a Replication Controller and a matching Service. + + Args: + context: Template context. See schema for context properties + + Returns: + A Container Manifest as a YAML string. + """ + # YAML config that we're going to create for both RC & Service + config = {'resources': []} + + name = context.env['name'] + container_name = context.properties.get('container_name', name) + namespace = context.properties.get('namespace', 'default') + + # Define things that the Service cares about + service_name = context.properties.get('service_name', name + '-service') + service_type = SERVICE_TYPE_COLLECTION + + # Define things that the Replication Controller (rc) cares about + rc_name = context.properties.get('rc_name', name + '-rc') + rc_type = RC_TYPE_COLLECTION + + service = { + 'name': service_name, + 'type': service_type, + 'properties': { + 'apiVersion': 'v1', + 'kind': 'Service', + 'namespace': namespace, + 'metadata': { + 'name': service_name, + 'labels': GenerateLabels(context, service_name), + }, + 'spec': { + 'ports': [GenerateServicePorts(context, container_name)], + 'selector': GenerateLabels(context, name) + } + } + } + set_up_external_lb = context.properties.get('external_service', None) + if set_up_external_lb: + service['properties']['spec']['type'] = 'LoadBalancer' + cluster_ip = context.properties.get('cluster_ip', None) + if cluster_ip: + service['properties']['spec']['clusterIP'] = cluster_ip + config['resources'].append(service) + + rc = { + 'name': rc_name, + 'type': rc_type, + 'properties': { + 'apiVersion': 'v1', + 'kind': 'ReplicationController', + 'namespace': namespace, + 'metadata': { + 'name': rc_name, + 'labels': GenerateLabels(context, rc_name), + }, + 'spec': { + 'replicas': context.properties['replicas'], + 'selector': GenerateLabels(context, name), + 'template': { + 'metadata': { + 'labels': GenerateLabels(context, name), + }, + 'spec': { + 'containers': [ + { + 'env': GenerateEnv(context), + 'name': container_name, + 'image': context.properties['image'], + 'ports': [ + { + 'name': container_name, + 'containerPort': context.properties['container_port'], + } + ], + } + ], + } + } + } + } + } + + # Set up volume mounts + if context.properties.get('volumes', None): + rc['properties']['spec']['template']['spec']['containers'][0]['volumeMounts'] = [] + rc['properties']['spec']['template']['spec']['volumes'] = [] + for volume in context.properties['volumes']: + # mountPath should be unique + volume_name = volume['mount_path'].replace('/', '-').lstrip('-') + '-storage' + rc['properties']['spec']['template']['spec']['containers'][0]['volumeMounts'].append( + { + 'name': volume_name, + 'mountPath': volume['mount_path'] + } + ) + del volume['mount_path'] + volume['name'] = volume_name + rc['properties']['spec']['template']['spec']['volumes'].append(volume) + + if context.properties.get('privileged', False): + rc['properties']['spec']['template']['spec']['containers'][0]['securityContext'] = { + 'privileged': True + } + + config['resources'].append(rc) + return yaml.dump(config) + + +# Generates labels either from the context.properties['labels'] or generates +# a default label 'name':name +def GenerateLabels(context, name): + """Generates labels from context.properties['labels'] or creates default. + + We make a deep copy of the context.properties['labels'] section to avoid + linking in the yaml document, which I believe reduces readability of the + expanded template. If no labels are given, generate a default 'name':name. + + Args: + context: Template context, which can contain the following properties: + labels - Labels to generate + + Returns: + A dict containing labels in a name:value format + """ + tmp_labels = context.properties.get('labels', None) + ret_labels = {'name': name} + if isinstance(tmp_labels, dict): + for key, value in tmp_labels.iteritems(): + ret_labels[key] = value + return ret_labels + + +def GenerateServicePorts(context, name): + """Generates a ports section for a service. + + Args: + context: Template context, which can contain the following properties: + service_port - Port to use for the service + target_port - Target port for the service + protocol - Protocol to use. + + Returns: + A dict containing a port definition + """ + service_port = context.properties.get('service_port', None) + target_port = context.properties.get('target_port', None) + protocol = context.properties.get('protocol') + + ports = {} + if name: + ports['name'] = name + if service_port: + ports['port'] = service_port + if target_port: + ports['targetPort'] = target_port + if protocol: + ports['protocol'] = protocol + + return ports + +def GenerateEnv(context): + """Generates environmental variables for a pod. + + Args: + context: Template context, which can contain the following properties: + env - Environment variables to set. + + Returns: + A list containing env variables in dict format {name: 'name', value: 'value'} + """ + env = [] + tmp_env = context.properties.get('env', []) + for entry in tmp_env: + if isinstance(entry, dict): + env.append({'name': entry.get('name'), 'value': entry.get('value')}) + return env diff --git a/templates/replicatedservice/v2/replicatedservice.py.schema b/templates/replicatedservice/v2/replicatedservice.py.schema new file mode 100644 index 000000000..712ffd315 --- /dev/null +++ b/templates/replicatedservice/v2/replicatedservice.py.schema @@ -0,0 +1,91 @@ +info: + title: Replicated Service + description: | + Defines a ReplicatedService type by creating both a Service and an RC. + + This module creates a typical abstraction for running a service in a + Kubernetes cluster, namely a replication controller and a service packaged + together into a single unit. + +required: +- image + +properties: + container_name: + type: string + description: Name to use for container. If omitted, name is used. + service_name: + type: string + description: Name to use for service. If omitted, name-service is used. + namespace: + type: string + description: Namespace to create resources in. If omitted, 'default' is + used. + default: default + protocol: + type: string + description: Protocol to use for the service. + service_port: + type: int + description: Port to use for the service. + target_port: + type: int + description: Target port to use for the service. + container_port: + type: int + description: Port to use for the container. + replicas: + type: int + description: Number of replicas to create in RC. + image: + type: string + description: Docker image to use for replicas. + labels: + type: object + description: Labels to apply. + env: + type: array + description: Environment variables to apply. + properties: + name: + type: string + value: + type: string + external_service: + type: boolean + description: If set to true, enable external load balancer. + cluster_ip: + type: string + description: IP to use for the service + privileged: + type: boolean + description: If set to true, enable privileged container + volumes: + type: array + description: Volumes to mount. + items: + type: object + properties: + mounth_path: + type: string + description: Path to mount volume + # See https://cloud.google.com/container-engine/docs/spec-schema?hl=en for possible volumes. Since we only use gcePersistentDisk and NFS in our examples we have only added these ones. + oneOf: + gcePersistentDisk: + pdName: + type: string + description: Persistent's disk name + fsType: + type: string + description: Filesystem type of the persistent disk + nfs: + server: + type: string + description: The hostname or IP address of the NFS server + path: + type: string + description: The path that is exported by the NFS server + readOnly: + type: boolean + description: Forces the NFS export to be mounted with read-only permissions + From f9e41a8755bc9fde24bb13674689a9e00a6f3a3a Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 15:36:03 -0800 Subject: [PATCH 16/33] Adds Wordpress example --- examples/wordpress/README.md | 157 ++++++++++++++ examples/wordpress/images/nginx/Dockerfile | 2 + examples/wordpress/images/nginx/Makefile | 18 ++ examples/wordpress/images/nginx/default.conf | 48 +++++ examples/wordpress/wordpress-resources.yaml | 11 + examples/wordpress/wordpress.jinja | 80 ++++++++ examples/wordpress/wordpress.jinja.schema | 69 +++++++ .../replicatedservice/v2/replicatedservice.py | 194 ++++++++++++++++++ .../v2/replicatedservice.py.schema | 91 ++++++++ 9 files changed, 670 insertions(+) create mode 100644 examples/wordpress/README.md create mode 100644 examples/wordpress/images/nginx/Dockerfile create mode 100644 examples/wordpress/images/nginx/Makefile create mode 100644 examples/wordpress/images/nginx/default.conf create mode 100644 examples/wordpress/wordpress-resources.yaml create mode 100644 examples/wordpress/wordpress.jinja create mode 100644 examples/wordpress/wordpress.jinja.schema create mode 100644 types/replicatedservice/v2/replicatedservice.py create mode 100644 types/replicatedservice/v2/replicatedservice.py.schema diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md new file mode 100644 index 000000000..14cbc2ae9 --- /dev/null +++ b/examples/wordpress/README.md @@ -0,0 +1,157 @@ +# Wordpress Example + +Welcome to the Wordpress example. It shows you how to deploy a Wordpress application using Deployment Manager. + +## Prerequisites + + +### Deployment Manager +First, make sure DM is installed in your Kubernetes cluster by following the instructions in the top level +[README.md](../../README.md). + +### Google Cloud Resources +The Wordpress application will make use of several persistent disks, which we will host on Google Cloud. To create these disks we will create a deployment using Google Cloud Deployment Manager: +```gcloud deployment-manager deployments create wordpress-resources --config wordpress-resources.yaml``` + +where `wordpress-resources.yaml` looks as follows: + +``` +resources: +- name: nfs-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +- name: mysql-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +``` + +### Privileged containers +To use NFS we need to be able to launch privileged containers. If your Kubernetes cluster doesn't support this you can either manually change this in every Kubernetes minion or you could set the flag at `kubernetes/saltbase/pillar/privilege.sls` to true and (re)launch your Kubernetes cluster. Once Kubernetes 1.1 releases this should be enabled by default. + + +## Understanding the Wordpress example template + +Let's take a closer look at the template used by the Wordpress example. The Wordpress application consists of 4 microservices: an nginx service, a wordpress-php service, a MySQL service, and an NFS service. The architecture looks as follows: + +![Architecture](architecture.png) + +### Variables +The template contains the following variables: + +``` +{% set PROJECT = properties['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = properties['nfs-server'] or {} %} +{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} +{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} +{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} +{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} +{% set NGINX = properties['nginx'] or {} %} +{% set NGINX_PORT = 80 %} +{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} +{% set WORDPRESS_PHP = properties['wordpress-php'] or {} %} +{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} +{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} +{% set MYSQL = properties['mysql'] or {} %} +{% set MYSQL_PORT = MYSQL['port'] or 3306 %} +{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} +{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} +{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +``` + +### Nginx service +The nginx service is a replicated service with 2 replicas: + +``` +- name: nginx + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NGINX_PORT }} + container_port: {{ NGINX_PORT }} + replicas: {{ NGINX_REPLICAS }} + external_service: true + image: gcr.io/{{ PROJECT }}/nginx:latest + volumes: + - mount_path: /var/www/html + nfs: + server: {{ NFS_SERVER_IP }} + path: / +``` + +The nginx image builds upon the standard nginx image and simply copies a custom configuration file. + +### Wordpress-php service +The wordpress-php service is a replicated service with 2 replicas: + +``` +- name: wordpress-php + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_name: wordpress-php + service_port: {{ WORDPRESS_PHP_PORT }} + container_port: {{ WORDPRESS_PHP_PORT }} + replicas: 2 + image: wordpress:fpm + env: + - name: WORDPRESS_DB_PASSWORD + value: {{ MYSQL_PASSWORD }} + - name: WORDPRESS_DB_HOST + value: mysql-service + volumes: + - mount_path: /var/www/html + nfs: + server: {{ NFS_SERVER_IP }} + path: / +``` + +### MySQL service +The MySQL service is a replicated service with a single replica: + +``` +- name: mysql + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ MYSQL_PORT }} + container_port: {{ MYSQL_PORT }} + replicas: 1 + image: mysql:5.6 + env: + - name: MYSQL_ROOT_PASSWORD + value: {{ MYSQL_PASSWORD }} + volumes: + - mount_path: /var/lib/mysql + gcePersistentDisk: + pdName: {{ MYSQL_DISK }} + fsType: {{ MYSQL_DISK_FSTYPE }} +``` + +### NFS service +The NFS service is a replicated service with a single replica: + +``` +- name: nfs-server + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NFS_SERVER_PORT }} + container_port: {{ NFS_SERVER_PORT }} + replicas: 1 # Has to be 1 because of the persistent disk + image: jsafrane/nfs-data + privileged: true + cluster_ip: {{ NFS_SERVER_IP }} + volumes: + - mount_path: /mnt/data + gcePersistentDisk: + pdName: {{ NFS_SERVER_DISK }} + fsType: {{ NFS_SERVER_DISK_FSTYPE }} +``` + +## Deploying Wordpress +We can now deploy Wordpress using: + +``` +dm deploy examples/wordpress/wordpress.jinja +``` + diff --git a/examples/wordpress/images/nginx/Dockerfile b/examples/wordpress/images/nginx/Dockerfile new file mode 100644 index 000000000..ce14e868d --- /dev/null +++ b/examples/wordpress/images/nginx/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx +COPY default.conf /etc/nginx/conf.d/default.conf diff --git a/examples/wordpress/images/nginx/Makefile b/examples/wordpress/images/nginx/Makefile new file mode 100644 index 000000000..d07e33213 --- /dev/null +++ b/examples/wordpress/images/nginx/Makefile @@ -0,0 +1,18 @@ +.PHONY: all build push clean + +PREFIX = gcr.io/$(PROJECT) +IMAGE = nginx +TAG = latest + +DIR = . + +all: build + +build: + docker build -t $(PREFIX)/$(IMAGE):$(TAG) $(DIR) + +push: build + gcloud docker push $(PREFIX)/$(IMAGE):$(TAG) + +clean: + docker rmi $(PREFIX)/$(IMAGE):$(TAG) diff --git a/examples/wordpress/images/nginx/default.conf b/examples/wordpress/images/nginx/default.conf new file mode 100644 index 000000000..af2d7ebf8 --- /dev/null +++ b/examples/wordpress/images/nginx/default.conf @@ -0,0 +1,48 @@ +upstream phpcgi { + server wordpress-php:9000; +} + +server { + listen 80 ; + + root /var/www/html; + index index.php index.html index.htm; + + server_name localhost; + + location / { + try_files $uri $uri/ =404; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + + # With php5-cgi alone: + fastcgi_pass phpcgi; + + # With php5-fpm: + fastcgi_index index.php; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param DOCUMENT_URI $document_uri; + fastcgi_param DOCUMENT_ROOT $document_root; + fastcgi_param SERVER_PROTOCOL $server_protocol; + + fastcgi_param GATEWAY_INTERFACE CGI/1.1; + fastcgi_param SERVER_SOFTWARE nginx; + + fastcgi_param REMOTE_ADDR $remote_addr; + fastcgi_param REMOTE_PORT $remote_port; + fastcgi_param SERVER_ADDR $server_addr; + fastcgi_param SERVER_PORT $server_port; + fastcgi_param SERVER_NAME $server_name; + #include fastcgi_params; + } +} diff --git a/examples/wordpress/wordpress-resources.yaml b/examples/wordpress/wordpress-resources.yaml new file mode 100644 index 000000000..b66bfec32 --- /dev/null +++ b/examples/wordpress/wordpress-resources.yaml @@ -0,0 +1,11 @@ +resources: +- name: nfs-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +- name: mysql-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja new file mode 100644 index 000000000..1aedda278 --- /dev/null +++ b/examples/wordpress/wordpress.jinja @@ -0,0 +1,80 @@ +{% set PROJECT = properties['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = properties['nfs-server'] or {} %} +{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} +{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} +{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} +{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} +{% set NGINX = properties['nginx'] or {} %} +{% set NGINX_PORT = 80 %} +{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} +{% set WORDPRESS_PHP = properties['wordpress-php'] or {} %} +{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} +{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} +{% set MYSQL = properties['mysql'] or {} %} +{% set MYSQL_PORT = MYSQL['port'] or 3306 %} +{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} +{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} +{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +imports: +- path: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +resources: +- name: nfs-server + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NFS_SERVER_PORT }} + container_port: {{ NFS_SERVER_PORT }} + replicas: 1 # Has to be 1 because of the persistent disk + image: jsafrane/nfs-data + privileged: true + cluster_ip: {{ NFS_SERVER_IP }} + volumes: + - mount_path: /mnt/data + gcePersistentDisk: + pdName: {{ NFS_SERVER_DISK }} + fsType: {{ NFS_SERVER_DISK_FSTYPE }} +- name: nginx + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ NGINX_PORT }} + container_port: {{ NGINX_PORT }} + replicas: {{ NGINX_REPLICAS }} + external_service: true + image: gcr.io/{{ PROJECT }}/nginx:latest + volumes: + - mount_path: /var/www/html + nfs: + server: {{ NFS_SERVER_IP }} + path: / +- name: mysql + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ MYSQL_PORT }} + container_port: {{ MYSQL_PORT }} + replicas: 1 + image: mysql:5.6 + env: + - name: MYSQL_ROOT_PASSWORD + value: {{ MYSQL_PASSWORD }} + volumes: + - mount_path: /var/lib/mysql + gcePersistentDisk: + pdName: {{ MYSQL_DISK }} + fsType: {{ MYSQL_DISK_FSTYPE }} +- name: wordpress-php + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + properties: + service_name: wordpress-php + service_port: {{ WORDPRESS_PHP_PORT }} + container_port: {{ WORDPRESS_PHP_PORT }} + replicas: 2 + image: wordpress:fpm + env: + - name: WORDPRESS_DB_PASSWORD + value: {{ MYSQL_PASSWORD }} + - name: WORDPRESS_DB_HOST + value: mysql-service + volumes: + - mount_path: /var/www/html + nfs: + server: {{ NFS_SERVER_IP }} + path: / diff --git a/examples/wordpress/wordpress.jinja.schema b/examples/wordpress/wordpress.jinja.schema new file mode 100644 index 000000000..6124862f6 --- /dev/null +++ b/examples/wordpress/wordpress.jinja.schema @@ -0,0 +1,69 @@ +info: + title: Wordpress + description: | + Defines a Wordpress website by defining four replicatedservices: an NFS service, an NGINX service, an Wordpress-PHP service, and a MySQL service. + + The NGINX service and the Wordpress-fpm service both use NFS to share files. + +properties: + project: + type: string + default: dm-k8s-testing + description: Project location to load the images from. + nfs-service: + type: object + properties: + ip: + type: string + default: 10.0.253.247 + description: The IP of the NFS service. + port: + type: int + default: 2049 + description: The port of the NFS service. + disk: + type: string + default: nfs-disk + description: The name of the persistent disk the NFS service uses. + fstype: + type: string + default: ext4 + description: The filesystem the disk of the NFS service uses. + nginx: + type: object + properties: + replicas: + type: int + default: 2 + description: The number of replicas for the nginx service. + wordpress-php: + type: object + properties: + replicas: + type: int + default: 2 + description: The number of replicas for the wordpress-php service. + port: + type: int + default: 9000 + description: The port the wordpress-php service runs on. + mysql: + type: object + properties: + port: + type: int + default: 3306 + description: The port the MySQL service runs on. + password: + type: string + default: mysql-password + description: The root password of the MySQL service. + disk: + type: string + default: mysql-disk + description: The name of the persistent disk the MySQL service uses. + fstype: + type: string + default: ext4 + description: The filesystem the disk of the MySQL service uses. + diff --git a/types/replicatedservice/v2/replicatedservice.py b/types/replicatedservice/v2/replicatedservice.py new file mode 100644 index 000000000..a6ef0d71e --- /dev/null +++ b/types/replicatedservice/v2/replicatedservice.py @@ -0,0 +1,194 @@ +"""Defines a ReplicatedService type by creating both a Service and an RC. + +This module creates a typical abstraction for running a service in a +Kubernetes cluster, namely a replication controller and a service packaged +together into a single unit. +""" + +import yaml + +SERVICE_TYPE_COLLECTION = 'Service' +RC_TYPE_COLLECTION = 'ReplicationController' + + +def GenerateConfig(context): + """Generates a Replication Controller and a matching Service. + + Args: + context: Template context. See schema for context properties + + Returns: + A Container Manifest as a YAML string. + """ + # YAML config that we're going to create for both RC & Service + config = {'resources': []} + + name = context.env['name'] + container_name = context.properties.get('container_name', name) + namespace = context.properties.get('namespace', 'default') + + # Define things that the Service cares about + service_name = context.properties.get('service_name', name + '-service') + service_type = SERVICE_TYPE_COLLECTION + + # Define things that the Replication Controller (rc) cares about + rc_name = context.properties.get('rc_name', name + '-rc') + rc_type = RC_TYPE_COLLECTION + + service = { + 'name': service_name, + 'type': service_type, + 'properties': { + 'apiVersion': 'v1', + 'kind': 'Service', + 'namespace': namespace, + 'metadata': { + 'name': service_name, + 'labels': GenerateLabels(context, service_name), + }, + 'spec': { + 'ports': [GenerateServicePorts(context, container_name)], + 'selector': GenerateLabels(context, name) + } + } + } + set_up_external_lb = context.properties.get('external_service', None) + if set_up_external_lb: + service['properties']['spec']['type'] = 'LoadBalancer' + cluster_ip = context.properties.get('cluster_ip', None) + if cluster_ip: + service['properties']['spec']['clusterIP'] = cluster_ip + config['resources'].append(service) + + rc = { + 'name': rc_name, + 'type': rc_type, + 'properties': { + 'apiVersion': 'v1', + 'kind': 'ReplicationController', + 'namespace': namespace, + 'metadata': { + 'name': rc_name, + 'labels': GenerateLabels(context, rc_name), + }, + 'spec': { + 'replicas': context.properties['replicas'], + 'selector': GenerateLabels(context, name), + 'template': { + 'metadata': { + 'labels': GenerateLabels(context, name), + }, + 'spec': { + 'containers': [ + { + 'env': GenerateEnv(context), + 'name': container_name, + 'image': context.properties['image'], + 'ports': [ + { + 'name': container_name, + 'containerPort': context.properties['container_port'], + } + ], + } + ], + } + } + } + } + } + + # Set up volume mounts + if context.properties.get('volumes', None): + rc['properties']['spec']['template']['spec']['containers'][0]['volumeMounts'] = [] + rc['properties']['spec']['template']['spec']['volumes'] = [] + for volume in context.properties['volumes']: + # mountPath should be unique + volume_name = volume['mount_path'].replace('/', '-').lstrip('-') + '-storage' + rc['properties']['spec']['template']['spec']['containers'][0]['volumeMounts'].append( + { + 'name': volume_name, + 'mountPath': volume['mount_path'] + } + ) + del volume['mount_path'] + volume['name'] = volume_name + rc['properties']['spec']['template']['spec']['volumes'].append(volume) + + if context.properties.get('privileged', False): + rc['properties']['spec']['template']['spec']['containers'][0]['securityContext'] = { + 'privileged': True + } + + config['resources'].append(rc) + return yaml.dump(config) + + +# Generates labels either from the context.properties['labels'] or generates +# a default label 'name':name +def GenerateLabels(context, name): + """Generates labels from context.properties['labels'] or creates default. + + We make a deep copy of the context.properties['labels'] section to avoid + linking in the yaml document, which I believe reduces readability of the + expanded template. If no labels are given, generate a default 'name':name. + + Args: + context: Template context, which can contain the following properties: + labels - Labels to generate + + Returns: + A dict containing labels in a name:value format + """ + tmp_labels = context.properties.get('labels', None) + ret_labels = {'name': name} + if isinstance(tmp_labels, dict): + for key, value in tmp_labels.iteritems(): + ret_labels[key] = value + return ret_labels + + +def GenerateServicePorts(context, name): + """Generates a ports section for a service. + + Args: + context: Template context, which can contain the following properties: + service_port - Port to use for the service + target_port - Target port for the service + protocol - Protocol to use. + + Returns: + A dict containing a port definition + """ + service_port = context.properties.get('service_port', None) + target_port = context.properties.get('target_port', None) + protocol = context.properties.get('protocol') + + ports = {} + if name: + ports['name'] = name + if service_port: + ports['port'] = service_port + if target_port: + ports['targetPort'] = target_port + if protocol: + ports['protocol'] = protocol + + return ports + +def GenerateEnv(context): + """Generates environmental variables for a pod. + + Args: + context: Template context, which can contain the following properties: + env - Environment variables to set. + + Returns: + A list containing env variables in dict format {name: 'name', value: 'value'} + """ + env = [] + tmp_env = context.properties.get('env', []) + for entry in tmp_env: + if isinstance(entry, dict): + env.append({'name': entry.get('name'), 'value': entry.get('value')}) + return env diff --git a/types/replicatedservice/v2/replicatedservice.py.schema b/types/replicatedservice/v2/replicatedservice.py.schema new file mode 100644 index 000000000..712ffd315 --- /dev/null +++ b/types/replicatedservice/v2/replicatedservice.py.schema @@ -0,0 +1,91 @@ +info: + title: Replicated Service + description: | + Defines a ReplicatedService type by creating both a Service and an RC. + + This module creates a typical abstraction for running a service in a + Kubernetes cluster, namely a replication controller and a service packaged + together into a single unit. + +required: +- image + +properties: + container_name: + type: string + description: Name to use for container. If omitted, name is used. + service_name: + type: string + description: Name to use for service. If omitted, name-service is used. + namespace: + type: string + description: Namespace to create resources in. If omitted, 'default' is + used. + default: default + protocol: + type: string + description: Protocol to use for the service. + service_port: + type: int + description: Port to use for the service. + target_port: + type: int + description: Target port to use for the service. + container_port: + type: int + description: Port to use for the container. + replicas: + type: int + description: Number of replicas to create in RC. + image: + type: string + description: Docker image to use for replicas. + labels: + type: object + description: Labels to apply. + env: + type: array + description: Environment variables to apply. + properties: + name: + type: string + value: + type: string + external_service: + type: boolean + description: If set to true, enable external load balancer. + cluster_ip: + type: string + description: IP to use for the service + privileged: + type: boolean + description: If set to true, enable privileged container + volumes: + type: array + description: Volumes to mount. + items: + type: object + properties: + mounth_path: + type: string + description: Path to mount volume + # See https://cloud.google.com/container-engine/docs/spec-schema?hl=en for possible volumes. Since we only use gcePersistentDisk and NFS in our examples we have only added these ones. + oneOf: + gcePersistentDisk: + pdName: + type: string + description: Persistent's disk name + fsType: + type: string + description: Filesystem type of the persistent disk + nfs: + server: + type: string + description: The hostname or IP address of the NFS server + path: + type: string + description: The path that is exported by the NFS server + readOnly: + type: boolean + description: Forces the NFS export to be mounted with read-only permissions + From 9af50202c83264ba1d8525ce831eadede7de0f97 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 16:54:52 -0800 Subject: [PATCH 17/33] Updates Wordpress example --- examples/wordpress/README.md | 27 +++++++++++++++------------ examples/wordpress/wordpress.jinja | 11 ++++++----- examples/wordpress/wordpress.yaml | 6 ++++++ 3 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 examples/wordpress/wordpress.yaml diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 14cbc2ae9..96627374a 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -10,7 +10,7 @@ First, make sure DM is installed in your Kubernetes cluster by following the ins [README.md](../../README.md). ### Google Cloud Resources -The Wordpress application will make use of several persistent disks, which we will host on Google Cloud. To create these disks we will create a deployment using Google Cloud Deployment Manager: +The Wordpress application will make use of several persistent disks, which we will host on Google Cloud. To create these disks we will create a deployment using Google Cloud Deployment Manager: ```gcloud deployment-manager deployments create wordpress-resources --config wordpress-resources.yaml``` where `wordpress-resources.yaml` looks as follows: @@ -32,6 +32,12 @@ resources: ### Privileged containers To use NFS we need to be able to launch privileged containers. If your Kubernetes cluster doesn't support this you can either manually change this in every Kubernetes minion or you could set the flag at `kubernetes/saltbase/pillar/privilege.sls` to true and (re)launch your Kubernetes cluster. Once Kubernetes 1.1 releases this should be enabled by default. +### NFS Library +Currently the nfs-common library should be installed by default on Kubernetes nodes. In case nfs-common is not installed you can install it on all Kubernetes nodes using the following command: +``` +gcloud compute instances list | cut -d ' ' -f 1 | tail -n +2 | xargs -n1 gcloud compute ssh --command="sudo apt-get update;sudo apt-get -y install nfs-common" +``` + ## Understanding the Wordpress example template @@ -43,23 +49,20 @@ Let's take a closer look at the template used by the Wordpress example. The Word The template contains the following variables: ``` -{% set PROJECT = properties['project'] or 'dm-k8s-testing' %} -{% set NFS_SERVER = properties['nfs-server'] or {} %} +{% set PROPERTIES = properties or {} %} +{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %} {% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} {% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} {% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} {% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} -{% set NGINX = properties['nginx'] or {} %} +{% set NGINX = PROPERTIES['nginx'] or {} %} {% set NGINX_PORT = 80 %} {% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} -{% set WORDPRESS_PHP = properties['wordpress-php'] or {} %} +{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %} {% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} {% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} -{% set MYSQL = properties['mysql'] or {} %} -{% set MYSQL_PORT = MYSQL['port'] or 3306 %} -{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} -{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} -{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +{% set MYSQL = PROPERTIES['mysql'] or {} %} {% set MYSQL_PORT = MYSQL['port'] or 3306 %} {% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} ``` ### Nginx service @@ -126,7 +129,7 @@ The MySQL service is a replicated service with a single replica: gcePersistentDisk: pdName: {{ MYSQL_DISK }} fsType: {{ MYSQL_DISK_FSTYPE }} -``` +``` ### NFS service The NFS service is a replicated service with a single replica: @@ -146,7 +149,7 @@ The NFS service is a replicated service with a single replica: gcePersistentDisk: pdName: {{ NFS_SERVER_DISK }} fsType: {{ NFS_SERVER_DISK_FSTYPE }} -``` +``` ## Deploying Wordpress We can now deploy Wordpress using: diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index 1aedda278..8c559a0d9 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -1,16 +1,17 @@ -{% set PROJECT = properties['project'] or 'dm-k8s-testing' %} -{% set NFS_SERVER = properties['nfs-server'] or {} %} +{% set PROPERTIES = properties or {} %} +{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %} {% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} {% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} {% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} {% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} -{% set NGINX = properties['nginx'] or {} %} +{% set NGINX = PROPERTIES['nginx'] or {} %} {% set NGINX_PORT = 80 %} {% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} -{% set WORDPRESS_PHP = properties['wordpress-php'] or {} %} +{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %} {% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} {% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} -{% set MYSQL = properties['mysql'] or {} %} +{% set MYSQL = PROPERTIES['mysql'] or {} %} {% set MYSQL_PORT = MYSQL['port'] or 3306 %} {% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} diff --git a/examples/wordpress/wordpress.yaml b/examples/wordpress/wordpress.yaml new file mode 100644 index 000000000..b401897ab --- /dev/null +++ b/examples/wordpress/wordpress.yaml @@ -0,0 +1,6 @@ +imports: +- path: wordpress.jinja + +resources: +- name: wordpress + type: wordpress.jinja From 79d06c4b2136d2a22b5a4c5e9739eda992420a47 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 17:08:29 -0800 Subject: [PATCH 18/33] Change hardcoded type urls from leendersr/deployment-manager to kubernetes/deployment-manager --- examples/wordpress/README.md | 8 ++++---- examples/wordpress/wordpress.jinja | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 96627374a..3778b0dd7 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -70,7 +70,7 @@ The nginx service is a replicated service with 2 replicas: ``` - name: nginx - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -91,7 +91,7 @@ The wordpress-php service is a replicated service with 2 replicas: ``` - name: wordpress-php - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} @@ -115,7 +115,7 @@ The MySQL service is a replicated service with a single replica: ``` - name: mysql - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ MYSQL_PORT }} container_port: {{ MYSQL_PORT }} @@ -136,7 +136,7 @@ The NFS service is a replicated service with a single replica: ``` - name: nfs-server - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NFS_SERVER_PORT }} container_port: {{ NFS_SERVER_PORT }} diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index 8c559a0d9..35c6a159d 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -17,10 +17,10 @@ {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} imports: -- path: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +- path: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py resources: - name: nfs-server - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NFS_SERVER_PORT }} container_port: {{ NFS_SERVER_PORT }} @@ -34,7 +34,7 @@ resources: pdName: {{ NFS_SERVER_DISK }} fsType: {{ NFS_SERVER_DISK_FSTYPE }} - name: nginx - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -47,7 +47,7 @@ resources: server: {{ NFS_SERVER_IP }} path: / - name: mysql - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_port: {{ MYSQL_PORT }} container_port: {{ MYSQL_PORT }} @@ -62,7 +62,7 @@ resources: pdName: {{ MYSQL_DISK }} fsType: {{ MYSQL_DISK_FSTYPE }} - name: wordpress-php - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} From c7cbb83f0307fe07dd461ae7db6db464266b14b1 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 17:13:04 -0800 Subject: [PATCH 19/33] Fix typo, we want to deploy wordpress.yaml not .jinja --- examples/wordpress/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 3778b0dd7..2f40c58df 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -155,6 +155,6 @@ The NFS service is a replicated service with a single replica: We can now deploy Wordpress using: ``` -dm deploy examples/wordpress/wordpress.jinja +dm deploy examples/wordpress/wordpress.yaml ``` From 9b7d724ed1d9533e28331be26009f162d1c315ee Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 17:15:19 -0800 Subject: [PATCH 20/33] Fix spelling --- examples/wordpress/wordpress.jinja.schema | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wordpress/wordpress.jinja.schema b/examples/wordpress/wordpress.jinja.schema index 6124862f6..215b47e1e 100644 --- a/examples/wordpress/wordpress.jinja.schema +++ b/examples/wordpress/wordpress.jinja.schema @@ -1,9 +1,9 @@ info: title: Wordpress description: | - Defines a Wordpress website by defining four replicatedservices: an NFS service, an NGINX service, an Wordpress-PHP service, and a MySQL service. + Defines a Wordpress website by defining four replicated services: an NFS service, an nginx service, a wordpress-php service, and a MySQL service. - The NGINX service and the Wordpress-fpm service both use NFS to share files. + The nginx service and the Wordpress-php service both use NFS to share files. properties: project: From 62080fd2d1d5714011f584d2e25dca30f35d3d78 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 17:26:00 -0800 Subject: [PATCH 21/33] Updates Makefile to use generic Docker registry --- examples/wordpress/images/nginx/Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/wordpress/images/nginx/Makefile b/examples/wordpress/images/nginx/Makefile index d07e33213..3974d978e 100644 --- a/examples/wordpress/images/nginx/Makefile +++ b/examples/wordpress/images/nginx/Makefile @@ -1,6 +1,7 @@ .PHONY: all build push clean -PREFIX = gcr.io/$(PROJECT) +DOCKER_REGISTRY = gcr.io +PREFIX = $(DOCKER_REGISTRY)/$(PROJECT) IMAGE = nginx TAG = latest @@ -12,7 +13,11 @@ build: docker build -t $(PREFIX)/$(IMAGE):$(TAG) $(DIR) push: build +ifeq ($(DOCKER_REGISTRY),gcr.io) gcloud docker push $(PREFIX)/$(IMAGE):$(TAG) +else + docker push $(PREFIX)/$(IMAGE):$(TAG) +endif clean: docker rmi $(PREFIX)/$(IMAGE):$(TAG) From 3bd87bea07889c3496bb01f81eaaf67a3824437e Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 17:40:43 -0800 Subject: [PATCH 22/33] Adds picture to README --- examples/wordpress/architecture.png | Bin 0 -> 33703 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/wordpress/architecture.png diff --git a/examples/wordpress/architecture.png b/examples/wordpress/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..853039e63312eaeebcbbe8cdb1c24f7c59c3f88e GIT binary patch literal 33703 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 literal 0 HcmV?d00001 From 2f76daf30ba80460662c382ce4eb8596fd3d1f02 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Sat, 14 Nov 2015 14:36:05 -0800 Subject: [PATCH 23/33] Improves readability --- examples/wordpress/wordpress-resources.yaml | 1 + examples/wordpress/wordpress.jinja | 2 ++ 2 files changed, 3 insertions(+) diff --git a/examples/wordpress/wordpress-resources.yaml b/examples/wordpress/wordpress-resources.yaml index b66bfec32..00f709de0 100644 --- a/examples/wordpress/wordpress-resources.yaml +++ b/examples/wordpress/wordpress-resources.yaml @@ -1,3 +1,4 @@ +# Google Cloud Deployment Manager template resources: - name: nfs-disk type: compute.v1.disk diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index 35c6a159d..ccc9646eb 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -16,8 +16,10 @@ {% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} + imports: - path: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + resources: - name: nfs-server type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py From 6ca04c520553b8608a697b70a299b33738963fcf Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 15:16:53 -0800 Subject: [PATCH 24/33] Updates Wordpress README for Kubernetes 1.1 release --- examples/wordpress/README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 2f40c58df..1c87b686a 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -1,10 +1,7 @@ # Wordpress Example - Welcome to the Wordpress example. It shows you how to deploy a Wordpress application using Deployment Manager. ## Prerequisites - - ### Deployment Manager First, make sure DM is installed in your Kubernetes cluster by following the instructions in the top level [README.md](../../README.md). @@ -30,17 +27,12 @@ resources: ``` ### Privileged containers -To use NFS we need to be able to launch privileged containers. If your Kubernetes cluster doesn't support this you can either manually change this in every Kubernetes minion or you could set the flag at `kubernetes/saltbase/pillar/privilege.sls` to true and (re)launch your Kubernetes cluster. Once Kubernetes 1.1 releases this should be enabled by default. +To use NFS we need to be able to launch privileged containers. Since the release of Kubernetes 1.1 privileged container support is enabled by default. If your Kubernetes cluster doesn't support privileged containers you need to manually change this by setting the flag at `kubernetes/saltbase/pillar/privilege.sls` to true. ### NFS Library -Currently the nfs-common library should be installed by default on Kubernetes nodes. In case nfs-common is not installed you can install it on all Kubernetes nodes using the following command: -``` -gcloud compute instances list | cut -d ' ' -f 1 | tail -n +2 | xargs -n1 gcloud compute ssh --command="sudo apt-get update;sudo apt-get -y install nfs-common" -``` - +Mounting NFS volumes requires NFS libraries. Since the release of Kubernetes 1.1 the NFS libraries are installed by default. If they are not installed on your Kubernetes cluster you need to install them manually. ## Understanding the Wordpress example template - Let's take a closer look at the template used by the Wordpress example. The Wordpress application consists of 4 microservices: an nginx service, a wordpress-php service, a MySQL service, and an NFS service. The architecture looks as follows: ![Architecture](architecture.png) From 8b47cdcee051d463e50f3aacd132aacf4a2d845b Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 15:53:15 -0800 Subject: [PATCH 25/33] Adds extra primitive types for the Wordpress example --- manager/manager/types.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manager/manager/types.go b/manager/manager/types.go index e9c95e728..8301a5bdb 100644 --- a/manager/manager/types.go +++ b/manager/manager/types.go @@ -25,6 +25,9 @@ var Primitives = map[string]bool{ "Service": true, "Namespace": true, "Volume": true, + "Endpoints": true, + "PersistentVolumeClaim": true, + "PersistentVolume": true, } // SchemaImport represents an import as declared in a schema file. From 307c2f89db7dbffdae1fe23689a189c899be9e53 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 16:01:08 -0800 Subject: [PATCH 26/33] Move types/* to templates/*" --- {types => templates}/replicatedservice/v2/replicatedservice.py | 0 .../replicatedservice/v2/replicatedservice.py.schema | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {types => templates}/replicatedservice/v2/replicatedservice.py (100%) rename {types => templates}/replicatedservice/v2/replicatedservice.py.schema (100%) diff --git a/types/replicatedservice/v2/replicatedservice.py b/templates/replicatedservice/v2/replicatedservice.py similarity index 100% rename from types/replicatedservice/v2/replicatedservice.py rename to templates/replicatedservice/v2/replicatedservice.py diff --git a/types/replicatedservice/v2/replicatedservice.py.schema b/templates/replicatedservice/v2/replicatedservice.py.schema similarity index 100% rename from types/replicatedservice/v2/replicatedservice.py.schema rename to templates/replicatedservice/v2/replicatedservice.py.schema From 88f6d2169eac83cc583f95937db9f9354b33f36d Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 16:48:14 -0800 Subject: [PATCH 27/33] Updates README to link to master/templates --- examples/wordpress/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 1c87b686a..b13173913 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -62,7 +62,7 @@ The nginx service is a replicated service with 2 replicas: ``` - name: nginx - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -83,7 +83,7 @@ The wordpress-php service is a replicated service with 2 replicas: ``` - name: wordpress-php - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} @@ -107,7 +107,7 @@ The MySQL service is a replicated service with a single replica: ``` - name: mysql - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ MYSQL_PORT }} container_port: {{ MYSQL_PORT }} @@ -128,7 +128,7 @@ The NFS service is a replicated service with a single replica: ``` - name: nfs-server - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NFS_SERVER_PORT }} container_port: {{ NFS_SERVER_PORT }} From e5908add8f680f428f517d1a79a4866e41e36dd5 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 16:49:56 -0800 Subject: [PATCH 28/33] Changes the Wordpress template so that it uses PersistentVolumeClaims --- examples/wordpress/wordpress.jinja | 50 +++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index ccc9646eb..45cdcb89f 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -18,16 +18,16 @@ {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} imports: -- path: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +- path: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py resources: - name: nfs-server - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NFS_SERVER_PORT }} container_port: {{ NFS_SERVER_PORT }} replicas: 1 # Has to be 1 because of the persistent disk - image: jsafrane/nfs-data + image: gcr.io/google_containers/volume-nfs privileged: true cluster_ip: {{ NFS_SERVER_IP }} volumes: @@ -35,8 +35,36 @@ resources: gcePersistentDisk: pdName: {{ NFS_SERVER_DISK }} fsType: {{ NFS_SERVER_DISK_FSTYPE }} +- name: nfs-pvc + type: PersistentVolumeClaim + properties: + kind: PersistentVolumeClaim + apiVersion: v1 + metadata: + name: nfs + spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Mi +- name: nfs-pv + type: PersistentVolume + properties: + apiVersion: v1 + kind: PersistentVolume + metadata: + name: nfs + spec: + capacity: + storage: 1Mi + accessModes: + - ReadWriteMany + nfs: + server: {{ NFS_SERVER_IP }} + path: "/" - name: nginx - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -45,11 +73,10 @@ resources: image: gcr.io/{{ PROJECT }}/nginx:latest volumes: - mount_path: /var/www/html - nfs: - server: {{ NFS_SERVER_IP }} - path: / + persistentVolumeClaim: + claimName: nfs - name: mysql - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ MYSQL_PORT }} container_port: {{ MYSQL_PORT }} @@ -64,7 +91,7 @@ resources: pdName: {{ MYSQL_DISK }} fsType: {{ MYSQL_DISK_FSTYPE }} - name: wordpress-php - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} @@ -78,6 +105,5 @@ resources: value: mysql-service volumes: - mount_path: /var/www/html - nfs: - server: {{ NFS_SERVER_IP }} - path: / + persistentVolumeClaim: + claimName: nfs From 492583fabe252cafb5865f72dde5b707180b36d6 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 16:59:26 -0800 Subject: [PATCH 29/33] Creates an NFS type --- examples/wordpress/wordpress.jinja | 50 ++++-------------------------- templates/nfs/v1/nfs.jinja | 49 +++++++++++++++++++++++++++++ templates/nfs/v1/nfs.schema | 1 + templates/nfs/v1/nfs.yaml | 2 ++ 4 files changed, 58 insertions(+), 44 deletions(-) create mode 100644 templates/nfs/v1/nfs.jinja create mode 100644 templates/nfs/v1/nfs.schema create mode 100644 templates/nfs/v1/nfs.yaml diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index 45cdcb89f..4c409e02b 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -17,52 +17,14 @@ {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} -imports: -- path: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py - resources: -- name: nfs-server - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py - properties: - service_port: {{ NFS_SERVER_PORT }} - container_port: {{ NFS_SERVER_PORT }} - replicas: 1 # Has to be 1 because of the persistent disk - image: gcr.io/google_containers/volume-nfs - privileged: true - cluster_ip: {{ NFS_SERVER_IP }} - volumes: - - mount_path: /mnt/data - gcePersistentDisk: - pdName: {{ NFS_SERVER_DISK }} - fsType: {{ NFS_SERVER_DISK_FSTYPE }} -- name: nfs-pvc - type: PersistentVolumeClaim - properties: - kind: PersistentVolumeClaim - apiVersion: v1 - metadata: - name: nfs - spec: - accessModes: - - ReadWriteMany - resources: - requests: - storage: 1Mi -- name: nfs-pv - type: PersistentVolume +- name: nfs + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja properties: - apiVersion: v1 - kind: PersistentVolume - metadata: - name: nfs - spec: - capacity: - storage: 1Mi - accessModes: - - ReadWriteMany - nfs: - server: {{ NFS_SERVER_IP }} - path: "/" + ip: {{ NFS_SERVER_IP }} + port: {{ NFS_SERVER_PORT }} + disk: {{ NFS_SERVER_DISK }} + fstype: {{NFS_SERVER_DISK_FSTYPE }} - name: nginx type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: diff --git a/templates/nfs/v1/nfs.jinja b/templates/nfs/v1/nfs.jinja new file mode 100644 index 000000000..11ee661a2 --- /dev/null +++ b/templates/nfs/v1/nfs.jinja @@ -0,0 +1,49 @@ +{% set PROPERTIES = properties or {} %} +{% set SERVER_IP = PROPERTIES['ip'] or '10.0.253.247' %} +{% set SERVER_PORT = PROPERTIES['port'] or 2049 %} +{% set SERVER_DISK = PROPERTIES['disk'] or 'nfs-disk' %} +{% set SERVER_DISK_FSTYPE = PROPERTIES['fstype'] or 'ext4' %} + +resources: +- name: nfs-server + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ SERVER_PORT }} + container_port: {{ SERVER_PORT }} + replicas: 1 # Has to be 1 because of the persistent disk + image: gcr.io/google_containers/volume-nfs + privileged: true + cluster_ip: {{ SERVER_IP }} + volumes: + - mount_path: /mnt/data + gcePersistentDisk: + pdName: {{ SERVER_DISK }} + fsType: {{ SERVER_DISK_FSTYPE }} +- name: nfs-pvc + type: PersistentVolumeClaim + properties: + kind: PersistentVolumeClaim + apiVersion: v1 + metadata: + name: nfs + spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Mi +- name: nfs-pv + type: PersistentVolume + properties: + apiVersion: v1 + kind: PersistentVolume + metadata: + name: nfs + spec: + capacity: + storage: 1Mi + accessModes: + - ReadWriteMany + nfs: + server: {{ SERVER_IP }} + path: "/" diff --git a/templates/nfs/v1/nfs.schema b/templates/nfs/v1/nfs.schema new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/templates/nfs/v1/nfs.schema @@ -0,0 +1 @@ + diff --git a/templates/nfs/v1/nfs.yaml b/templates/nfs/v1/nfs.yaml new file mode 100644 index 000000000..4d5c68d8a --- /dev/null +++ b/templates/nfs/v1/nfs.yaml @@ -0,0 +1,2 @@ +- name: nfs + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja From af6b5a7a0068d5ce40cf15cc47e335ef9bc46f6d Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 17:42:47 -0800 Subject: [PATCH 30/33] Updates README and adds clain-name property to the NFS type --- examples/wordpress/README.md | 35 +++++++++++++---------------------- templates/nfs/v1/nfs.jinja | 7 ++++--- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index b13173913..d414fcc5c 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -62,7 +62,7 @@ The nginx service is a replicated service with 2 replicas: ``` - name: nginx - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -71,9 +71,8 @@ The nginx service is a replicated service with 2 replicas: image: gcr.io/{{ PROJECT }}/nginx:latest volumes: - mount_path: /var/www/html - nfs: - server: {{ NFS_SERVER_IP }} - path: / + persistentVolumeClaim: + claimName: nfs ``` The nginx image builds upon the standard nginx image and simply copies a custom configuration file. @@ -83,7 +82,7 @@ The wordpress-php service is a replicated service with 2 replicas: ``` - name: wordpress-php - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} @@ -97,9 +96,8 @@ The wordpress-php service is a replicated service with 2 replicas: value: mysql-service volumes: - mount_path: /var/www/html - nfs: - server: {{ NFS_SERVER_IP }} - path: / + persistentVolumeClaim: + claimName: nfs ``` ### MySQL service @@ -124,23 +122,16 @@ The MySQL service is a replicated service with a single replica: ``` ### NFS service -The NFS service is a replicated service with a single replica: +The NFS service is a replicated service with a single replica that is available as a type: ``` -- name: nfs-server - type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +- name: nfs + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja properties: - service_port: {{ NFS_SERVER_PORT }} - container_port: {{ NFS_SERVER_PORT }} - replicas: 1 # Has to be 1 because of the persistent disk - image: jsafrane/nfs-data - privileged: true - cluster_ip: {{ NFS_SERVER_IP }} - volumes: - - mount_path: /mnt/data - gcePersistentDisk: - pdName: {{ NFS_SERVER_DISK }} - fsType: {{ NFS_SERVER_DISK_FSTYPE }} + ip: {{ NFS_SERVER_IP }} + port: {{ NFS_SERVER_PORT }} + disk: {{ NFS_SERVER_DISK }} + fstype: {{NFS_SERVER_DISK_FSTYPE }} ``` ## Deploying Wordpress diff --git a/templates/nfs/v1/nfs.jinja b/templates/nfs/v1/nfs.jinja index 11ee661a2..d8aca3117 100644 --- a/templates/nfs/v1/nfs.jinja +++ b/templates/nfs/v1/nfs.jinja @@ -3,9 +3,10 @@ {% set SERVER_PORT = PROPERTIES['port'] or 2049 %} {% set SERVER_DISK = PROPERTIES['disk'] or 'nfs-disk' %} {% set SERVER_DISK_FSTYPE = PROPERTIES['fstype'] or 'ext4' %} +{% set CLAIM_NAME = PROPERTIES['claim-name'] or 'nfs' %} resources: -- name: nfs-server +- name: nfs type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ SERVER_PORT }} @@ -25,7 +26,7 @@ resources: kind: PersistentVolumeClaim apiVersion: v1 metadata: - name: nfs + name: {{ CLAIM_NAME }} spec: accessModes: - ReadWriteMany @@ -38,7 +39,7 @@ resources: apiVersion: v1 kind: PersistentVolume metadata: - name: nfs + name: {{ CLAIM_NAME }} spec: capacity: storage: 1Mi From 09a659a22dde6bc8d009d908412777109e5f293b Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 18:30:27 -0800 Subject: [PATCH 31/33] Adds wordpress.yaml code to README --- examples/wordpress/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index d414fcc5c..74d75abb0 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -141,3 +141,15 @@ We can now deploy Wordpress using: dm deploy examples/wordpress/wordpress.yaml ``` +where `wordpress.yaml` looks as follows: + +``` +imports: +- path: wordpress.jinja + +resources: +- name: wordpress + type: wordpress.jinja + properties: + project: +``` From be9b2a3b1449fcbf72220932da75fae16133dc3e Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Mon, 16 Nov 2015 18:43:17 -0800 Subject: [PATCH 32/33] Changes absolute url from leendersr/deployment-manager back to kubernetes/deployment-manager --- examples/wordpress/README.md | 6 +++--- examples/wordpress/wordpress.jinja | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md index 74d75abb0..9e2e57d06 100644 --- a/examples/wordpress/README.md +++ b/examples/wordpress/README.md @@ -62,7 +62,7 @@ The nginx service is a replicated service with 2 replicas: ``` - name: nginx - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -82,7 +82,7 @@ The wordpress-php service is a replicated service with 2 replicas: ``` - name: wordpress-php - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} @@ -126,7 +126,7 @@ The NFS service is a replicated service with a single replica that is available ``` - name: nfs - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/nfs/v1/nfs.jinja properties: ip: {{ NFS_SERVER_IP }} port: {{ NFS_SERVER_PORT }} diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja index 4c409e02b..08159e3cb 100644 --- a/examples/wordpress/wordpress.jinja +++ b/examples/wordpress/wordpress.jinja @@ -19,14 +19,14 @@ resources: - name: nfs - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/nfs/v1/nfs.jinja properties: ip: {{ NFS_SERVER_IP }} port: {{ NFS_SERVER_PORT }} disk: {{ NFS_SERVER_DISK }} fstype: {{NFS_SERVER_DISK_FSTYPE }} - name: nginx - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ NGINX_PORT }} container_port: {{ NGINX_PORT }} @@ -38,7 +38,7 @@ resources: persistentVolumeClaim: claimName: nfs - name: mysql - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_port: {{ MYSQL_PORT }} container_port: {{ MYSQL_PORT }} @@ -53,7 +53,7 @@ resources: pdName: {{ MYSQL_DISK }} fsType: {{ MYSQL_DISK_FSTYPE }} - name: wordpress-php - type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py properties: service_name: wordpress-php service_port: {{ WORDPRESS_PHP_PORT }} From fb54b7cdaafbcce4a2112a74740af46053a04623 Mon Sep 17 00:00:00 2001 From: Robert Leenders Date: Thu, 12 Nov 2015 15:36:03 -0800 Subject: [PATCH 33/33] Adds Wordpress example Adds wordpress.yaml code to README Changes absolute url from leendersr/deployment-manager back to kubernetes/deployment-manager --- examples/wordpress/README.md | 333 ++++++++++++++++++ examples/wordpress/architecture.png | Bin 0 -> 33703 bytes examples/wordpress/images/nginx/Dockerfile | 2 + examples/wordpress/images/nginx/Makefile | 42 +++ examples/wordpress/images/nginx/default.conf | 48 +++ examples/wordpress/wordpress-resources.yaml | 19 + examples/wordpress/wordpress.jinja | 185 ++++++++++ examples/wordpress/wordpress.jinja.schema | 81 +++++ examples/wordpress/wordpress.yaml | 6 + manager/manager/types.go | 3 + templates/nfs/v1/nfs.jinja | 50 +++ templates/nfs/v1/nfs.schema | 1 + templates/nfs/v1/nfs.yaml | 2 + .../replicatedservice/v2/replicatedservice.py | 194 ++++++++++ .../v2/replicatedservice.py.schema | 91 +++++ 15 files changed, 1057 insertions(+) create mode 100644 examples/wordpress/README.md create mode 100644 examples/wordpress/architecture.png create mode 100644 examples/wordpress/images/nginx/Dockerfile create mode 100644 examples/wordpress/images/nginx/Makefile create mode 100644 examples/wordpress/images/nginx/default.conf create mode 100644 examples/wordpress/wordpress-resources.yaml create mode 100644 examples/wordpress/wordpress.jinja create mode 100644 examples/wordpress/wordpress.jinja.schema create mode 100644 examples/wordpress/wordpress.yaml create mode 100644 templates/nfs/v1/nfs.jinja create mode 100644 templates/nfs/v1/nfs.schema create mode 100644 templates/nfs/v1/nfs.yaml create mode 100644 templates/replicatedservice/v2/replicatedservice.py create mode 100644 templates/replicatedservice/v2/replicatedservice.py.schema diff --git a/examples/wordpress/README.md b/examples/wordpress/README.md new file mode 100644 index 000000000..f2f7908d7 --- /dev/null +++ b/examples/wordpress/README.md @@ -0,0 +1,333 @@ +# Wordpress Example +<<<<<<< HEAD +<<<<<<< HEAD +Welcome to the Wordpress example. It shows you how to deploy a Wordpress application using Deployment Manager. + +## Prerequisites +======= + +Welcome to the Wordpress example. It shows you how to deploy a Wordpress application using Deployment Manager. + +## Prerequisites + + +>>>>>>> 320a0e9... Adds Wordpress example +======= +Welcome to the Wordpress example. It shows you how to deploy a Wordpress application using Deployment Manager. + +## Prerequisites +>>>>>>> eed0dda... Updates Wordpress README for Kubernetes 1.1 release +### Deployment Manager +First, make sure DM is installed in your Kubernetes cluster by following the instructions in the top level +[README.md](../../README.md). + +### Google Cloud Resources +<<<<<<< HEAD +<<<<<<< HEAD +The Wordpress application will make use of several persistent disks, which we will host on Google Cloud. To create these disks we will create a deployment using Google Cloud Deployment Manager: +======= +The Wordpress application will make use of several persistent disks, which we will host on Google Cloud. To create these disks we will create a deployment using Google Cloud Deployment Manager: +>>>>>>> 320a0e9... Adds Wordpress example +======= +The Wordpress application will make use of several persistent disks, which we will host on Google Cloud. To create these disks we will create a deployment using Google Cloud Deployment Manager: +>>>>>>> f7940f1... Updates Wordpress example +```gcloud deployment-manager deployments create wordpress-resources --config wordpress-resources.yaml``` + +where `wordpress-resources.yaml` looks as follows: + +``` +resources: +- name: nfs-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +- name: mysql-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +``` + +### Privileged containers +<<<<<<< HEAD +<<<<<<< HEAD +To use NFS we need to be able to launch privileged containers. Since the release of Kubernetes 1.1 privileged container support is enabled by default. If your Kubernetes cluster doesn't support privileged containers you need to manually change this by setting the flag at `kubernetes/saltbase/pillar/privilege.sls` to true. + +### NFS Library +Mounting NFS volumes requires NFS libraries. Since the release of Kubernetes 1.1 the NFS libraries are installed by default. If they are not installed on your Kubernetes cluster you need to install them manually. + +## Understanding the Wordpress example template +======= +To use NFS we need to be able to launch privileged containers. If your Kubernetes cluster doesn't support this you can either manually change this in every Kubernetes minion or you could set the flag at `kubernetes/saltbase/pillar/privilege.sls` to true and (re)launch your Kubernetes cluster. Once Kubernetes 1.1 releases this should be enabled by default. +======= +To use NFS we need to be able to launch privileged containers. Since the release of Kubernetes 1.1 privileged container support is enabled by default. If your Kubernetes cluster doesn't support privileged containers you need to manually change this by setting the flag at `kubernetes/saltbase/pillar/privilege.sls` to true. +>>>>>>> eed0dda... Updates Wordpress README for Kubernetes 1.1 release + +### NFS Library +Mounting NFS volumes requires NFS libraries. Since the release of Kubernetes 1.1 the NFS libraries are installed by default. If they are not installed on your Kubernetes cluster you need to install them manually. + +## Understanding the Wordpress example template +<<<<<<< HEAD + +>>>>>>> 320a0e9... Adds Wordpress example +======= +>>>>>>> eed0dda... Updates Wordpress README for Kubernetes 1.1 release +Let's take a closer look at the template used by the Wordpress example. The Wordpress application consists of 4 microservices: an nginx service, a wordpress-php service, a MySQL service, and an NFS service. The architecture looks as follows: + +![Architecture](architecture.png) + +### Variables +The template contains the following variables: + +``` +<<<<<<< HEAD +<<<<<<< HEAD +{% set PROPERTIES = properties or {} %} +{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %} +======= +{% set PROJECT = properties['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = properties['nfs-server'] or {} %} +>>>>>>> 320a0e9... Adds Wordpress example +======= +{% set PROPERTIES = properties or {} %} +{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %} +>>>>>>> f7940f1... Updates Wordpress example +{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} +{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} +{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} +{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} +<<<<<<< HEAD +<<<<<<< HEAD +{% set NGINX = PROPERTIES['nginx'] or {} %} +{% set NGINX_PORT = 80 %} +{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} +{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %} +{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} +{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} +{% set MYSQL = PROPERTIES['mysql'] or {} %} {% set MYSQL_PORT = MYSQL['port'] or 3306 %} {% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +======= +{% set NGINX = properties['nginx'] or {} %} +======= +{% set NGINX = PROPERTIES['nginx'] or {} %} +>>>>>>> f7940f1... Updates Wordpress example +{% set NGINX_PORT = 80 %} +{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} +{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %} +{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} +{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} +<<<<<<< HEAD +{% set MYSQL = properties['mysql'] or {} %} +{% set MYSQL_PORT = MYSQL['port'] or 3306 %} +{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} +{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} +{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +>>>>>>> 320a0e9... Adds Wordpress example +======= +{% set MYSQL = PROPERTIES['mysql'] or {} %} {% set MYSQL_PORT = MYSQL['port'] or 3306 %} {% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} {% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} {% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +>>>>>>> f7940f1... Updates Wordpress example +``` + +### Nginx service +The nginx service is a replicated service with 2 replicas: + +``` +- name: nginx +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +======= + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> 320a0e9... Adds Wordpress example +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> ebf3a33... Change hardcoded type urls from leendersr/deployment-manager to kubernetes/deployment-manager +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> 77312ee... Updates README to link to master/templates +======= + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> af6b5a7... Updates README and adds clain-name property to the NFS type +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> be9b2a3... Changes absolute url from leendersr/deployment-manager back to kubernetes/deployment-manager + properties: + service_port: {{ NGINX_PORT }} + container_port: {{ NGINX_PORT }} + replicas: {{ NGINX_REPLICAS }} + external_service: true + image: gcr.io/{{ PROJECT }}/nginx:latest + volumes: + - mount_path: /var/www/html + persistentVolumeClaim: + claimName: nfs +``` + +The nginx image builds upon the standard nginx image and simply copies a custom configuration file. + +### Wordpress-php service +The wordpress-php service is a replicated service with 2 replicas: + +``` +- name: wordpress-php +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +======= + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> 320a0e9... Adds Wordpress example +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> ebf3a33... Change hardcoded type urls from leendersr/deployment-manager to kubernetes/deployment-manager +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> 77312ee... Updates README to link to master/templates +======= + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> af6b5a7... Updates README and adds clain-name property to the NFS type +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> be9b2a3... Changes absolute url from leendersr/deployment-manager back to kubernetes/deployment-manager + properties: + service_name: wordpress-php + service_port: {{ WORDPRESS_PHP_PORT }} + container_port: {{ WORDPRESS_PHP_PORT }} + replicas: 2 + image: wordpress:fpm + env: + - name: WORDPRESS_DB_PASSWORD + value: {{ MYSQL_PASSWORD }} + - name: WORDPRESS_DB_HOST + value: mysql-service + volumes: + - mount_path: /var/www/html + persistentVolumeClaim: + claimName: nfs +``` + +### MySQL service +The MySQL service is a replicated service with a single replica: + +``` +- name: mysql +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +======= + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> 320a0e9... Adds Wordpress example +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> ebf3a33... Change hardcoded type urls from leendersr/deployment-manager to kubernetes/deployment-manager +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> 77312ee... Updates README to link to master/templates + properties: + service_port: {{ MYSQL_PORT }} + container_port: {{ MYSQL_PORT }} + replicas: 1 + image: mysql:5.6 + env: + - name: MYSQL_ROOT_PASSWORD + value: {{ MYSQL_PASSWORD }} + volumes: + - mount_path: /var/lib/mysql + gcePersistentDisk: + pdName: {{ MYSQL_DISK }} + fsType: {{ MYSQL_DISK_FSTYPE }} +<<<<<<< HEAD +<<<<<<< HEAD +``` +======= +``` +>>>>>>> 320a0e9... Adds Wordpress example +======= +``` +>>>>>>> f7940f1... Updates Wordpress example + +### NFS service +The NFS service is a replicated service with a single replica that is available as a type: + +``` +<<<<<<< HEAD +- name: nfs-server +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +======= + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> 320a0e9... Adds Wordpress example +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> ebf3a33... Change hardcoded type urls from leendersr/deployment-manager to kubernetes/deployment-manager +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> 77312ee... Updates README to link to master/templates + properties: + service_port: {{ NFS_SERVER_PORT }} + container_port: {{ NFS_SERVER_PORT }} + replicas: 1 # Has to be 1 because of the persistent disk + image: jsafrane/nfs-data + privileged: true + cluster_ip: {{ NFS_SERVER_IP }} + volumes: + - mount_path: /mnt/data + gcePersistentDisk: + pdName: {{ NFS_SERVER_DISK }} + fsType: {{ NFS_SERVER_DISK_FSTYPE }} +<<<<<<< HEAD +<<<<<<< HEAD +======= +- name: nfs + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/nfs/v1/nfs.jinja + properties: + ip: {{ NFS_SERVER_IP }} + port: {{ NFS_SERVER_PORT }} + disk: {{ NFS_SERVER_DISK }} + fstype: {{NFS_SERVER_DISK_FSTYPE }} +>>>>>>> af6b5a7... Updates README and adds clain-name property to the NFS type +``` +======= +``` +>>>>>>> 320a0e9... Adds Wordpress example +======= +``` +>>>>>>> f7940f1... Updates Wordpress example + +## Deploying Wordpress +We can now deploy Wordpress using: + +``` +<<<<<<< HEAD +<<<<<<< HEAD +dm deploy examples/wordpress/wordpress.yaml +======= +dm deploy examples/wordpress/wordpress.jinja +>>>>>>> 320a0e9... Adds Wordpress example +======= +dm deploy examples/wordpress/wordpress.yaml +>>>>>>> ef7e062... Fix typo, we want to deploy wordpress.yaml not .jinja +``` + +where `wordpress.yaml` looks as follows: + +``` +imports: +- path: wordpress.jinja + +resources: +- name: wordpress + type: wordpress.jinja + properties: + project: +``` diff --git a/examples/wordpress/architecture.png b/examples/wordpress/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..853039e63312eaeebcbbe8cdb1c24f7c59c3f88e GIT binary patch literal 33703 zcmeFZcT|&E`!*WH!Lb0Ns5Ai;1yLj*p-7ccK}0}7YA7O7LvNubIE*6FRHRoydM^o` zpaMbZHS{VVHHKbNz8&VB8Q$MI-#P27b=ErneEb7E$$p-_?|tuk-}iN0`w4oep~8HO z>lg$AVOG6&M+*Y^1^mfa|0@&tWjoUA1%dcORPQJ~_8eLoJ?d+0k*G+$q#j?US_Pf| zT?qcX}b;0gcFqQ^>JX<&X$JJG_*@%XogzjYonEW7`U za?zC&DXIP`7DDfTZ=oa;?Rn*l9J@cZdGDccCdtvT%F4Of*@VKmov}H&%p7{3{x?5Q z?8R*YzP?yO-}&fTa!)5)I&|0(xAvZqWv70uG)!S-+mBUlz2-}}%!rMiAsUIK0hW)H(#GhN*_!96_`p#NNzh& z@9sx7ElkPLd*e6EhFD~maC@zO?TN*ECgVe9nTcr3PLy^sw-oAgH;S@bYpkbd8YAjF z`q>h0XD~pjtvQkpqcn+WMyoR@3Airw_}w7$-S??|148)U#&dqvy7VL@Y7sH zyYgv`Eux%xIl?2hczAxXp!!q6Ad#@7c>?slDfAW7#88UYg3p+1D+_USX6Sl`!`gPt zY_VGECn=ZSALZq-SmWYiBw3J#`MDE})2g7(64~QjG!4^mh~&=GCKFg|s&hX(moFmj zF52^h)0QUR6(h;5_{Q0Z^{r)HYdbqb1-tE^1j)FXuV3Od2Fba%U!G7D_8I3|migx) z*O+%8T)=#6%VYkzScI!qr zeXHwG6RYk51$*0n2C?DZ)TYM|Pj>+k?i+4DrrZVd(XN{&L3Zf|Ep z1sqptOst&P^=4Kcc*QjSIBdR3Y@9mAdR%-`R?V*m$gfYe?KcPi0&M& z_IX5hWO1MWqOtfPm2exAp^*{as?pOB$SuZ)cNpLFl}vtD=N%=c+#WYs4`hdpdYe6! zob`~c+u9&4=DF6^p85NmEH}Rwv7&rivWKn9HdY&Wq65P@NV@BYWz64CEF1QaU0U}X znbeEO-`xgCGVf_02OU3XIuH{M&z@PL!R{q zDwaH5%g377jUV~@pE8UDm4-R)S3lBoLH6N) z9%xWIbh<6wk41WEP+e;HX(NxKNy<+DahK_Q#VcT4&}N+l6G{f^SRs%UhAXKI_cD7k zT%kKdEp5|HINs9sREG4E1WM)V5KRayi?faohMZR46`HIi&TRStQ!(E<$$U!&BiU$I zAB;mSBNpw8CUfB$qqA++_Znr~;#`fR@wA!>r>_aujxFZnCcE1*>*i)!3UO`!K9NAk zP%EfF4ZgpQ^-j0>`9_MBT7M+^uDEENPMilKN#BuFQaLWXgz2Cbve;VtB8I|xOlzl_ zh<}gq4%^2)L{H*z-fIc2ljwpN8T&xJR0GT+uefROY#+a-LS`)CxvmU>s->x{m2<1HgCQ$?qpZFhk>Y{&D! zI=7WyqE9=XpK_VRnfCj%u&!Ju4_BJ%SK(XHyTd=k+SItYxTA42|1V=T)+?A7JHbh% z2X0%aE;S;Cb83I#dh7@fYJJt6F#N;@_DiOn!p4kwSbVK#dnDns18tK0I$3DDlb9qV zxhJx_IwBP&y)eKonNaif8cD&ff|St`w*7PI>6i6cuj9*m!2V2w7gLXRZy~f>?SkX= z6Py+XU&wDNOe|af(Z_nOEY;@cgp{CT)nJPcpHuT%vfM^1J8svMP4_}NsKeZ}opYUUZvQ#M8#||a;MLHj{q+)H7oc}i{Wpy2znw~z4D#cH zKuAfUlNABZ-e z{kH{hkht?%>iZ8rQF0fi^e}$^(f_+U(LE~eylupO!~zZOupM}W0DU0O{(1Pp-h%7N zC3J^>^5%N#(f^F;zxuxKss{!OQ|c8crnlJdN{ZwlJ!FBtcO6!;|G&S}euKxh_WSPX z%qCEGEkZKJCnI4lzb^9jzAV$^P z{#{ozLTlVk;EQ>ftF7NR;?h3xBAArZgl$!+`r*okcKlpUqzlTvzy7Akw{e*(h*S|{ z!O_7UuFhQ9%`}lWt#L(xW{`}_+s0(Q2;_AisWyS1Lz0y4$mx-huqo{|C~tYkCzaC< zRlv9BrUm#(zK=@pbNfF!p4QaRfhWDSHSGN}>n3T0KKEe-+SY$%55lq!AGB3+G-LZ) zvu$IuOJoYgn20lRN@Dt&p^ebi$^~QbpIX!BMVN&XFGn*qrz;XTnd@Ve9v4&&3^LdE zp2+HDdSaV%C!$nvWi{t$cQu!P*U#6}mo9VJi`_2BvG6ddIw2`Bn(C$JZKTW`WZ=DH zE-w<%CbiI0e6qt-CPI1>?`Gp*!zgNERTI`7TmJ~XDv{yFT%V;r&-l7OjAVUlmX^r% zT8O=^c9XBpf%z6a^et&PzH3O40QF(w2FKy04<; zUV7o(fRr2yWp#M;rzXVfSqoc;n=+$l+~J)$;qov&Tiv`I1`hRL7sPW+CPR$rw4Dxf zz2}2UwbYij9J`KC)~U)NR~wfaR(p97io9;7QVxT@0dsx-sc}v5`HMRk;{Aw~a|z?+ zsX_X?W_Nubo-Cz2&GwIx*LOfe?I9UW(;DKN!YhUOJeujiWa%+sjopO3ncO-ZvPn%C zSyC-Np0uYI30s$dfDm`0r1~R6jEMO)B&XAfnl#R6rZp0VW26a!am!FQ98j!d516+8 zt+C^dYcZxtTTZX440zqv0x3r#^Xx16Lu*bwO0c#nXb| z-co&@9UWlPn%Y|J;EWcS<%W_fD$a?wF{yXxtq{3OD^pb?(0*dU*%&oE93i~*gt6#R zwoqn0Maf8JKZ52zH4IgrmX`ZbFcB~l+?1F z7*oFd{Ef@i#S9fHQOTBGI@v;tFNBengBhjV{>#RAk-i|`(jwUM&Hicq5Y~&Ss&$i1 z+G96OwAhL;mPSjN&LMabC*E17C9l4p%bzrPJ10KBcC6Q$067dYay+-c&8+dfT|UQv zGUHX8i0Q6Lm7U1=NKl4pUQi@o44%;KZK_jQ?V>y_5_fMW_PJP3f$f=SS`PEVyo$qx^awBeftMIDpO{A@F6H(uL z-pyOHf4#jG(Rr9NcILG()WS=)oK-7>T6SqHbFS3)!veANa&}Mh zpN6vi(*vQ($ocfOd9BebgPAyI=LeBvZL&%H5}$hP8oNQj8H$E0N}vQro9{6G268Er z9*D3M{2G_+{JR10u~Cy=Bz*enc;3}=tg)OU44>*8Wl4C9Y28+h-piQP^@;OL9EF4s z&V<%=7pj=&j$EYK{7l9jmX5PbS1;NgP0fDG*IABbdQvID*c11PzIsCmLl#~0?-o3- zr$-w$5wzAMCHT8}qNg2Waj=RJyE+&2u)bi)Rpgg|ZqAQ3o94*ND0~zJq1r+d$+yMN zq_1jp?`;r-ORICiU`^!|EGHw!ROQfmqy1qUkJ0zh3Vubu;Z-UvjbLppqeTK(_F;AA z#_sqR4XCJ<0eICjr5bF`Na$;sZb*o77Snlp{`e#s22q1DYKYfqX=j!(a0G3PuSUO% zYJX#MU92-qS<6-~T@BU46Sx*Viiuj^?Pk8yU&f%ng2AWMVdjL95y95KHl!DIJqeyw zk~6i8H6 zrUF@Dv1t9qL`-Oeh@gEw14qE(wju1b)|!YSKPV-|itHC1ifxMGEqR-sC|Zu@ zFF0Uqu$o-b$h(Z96pdvYNYjJrvN3(wg;ayzn4epxWMmkCl*Kd2`ci{MJ;^YY^cz9| zE6G@t&-BFbh^VDw6K+~QRM`f0DD7s92(n#@Uu*3KiV)NFKJK|qh8b8z9ZieSXmXp8 zw<=>%!rS}m2DfFaB^&Eb?Ya1@XeB1!V9-Q^<>m|`P9yomm0KOb3B-$Ad%ie z@jWM)UU)4GV-3k*@~R@sn+B#n=O}$Xt=~@Uc|=)&N=aXD!qvo`Q=w&^ zbsVDj6uCA_wNmf?vcdQL6W1u-Cl*?a4<(2^*I?Xo3kY#Rwj1j(ijF?M%&Rwa{?%52 z5u<2+o8HQ)Z=TSe#o(qpHsTU-$9BL}8{aSIF3q3XXz)3um`v3m)y!BrL{{e_;&iHH z)I&t&s9^u-B=oxqkxu~%| zSW?=$dpho}t#Ag@lh)o=sm;`*>_pc%OspkiR6*d`aJ6UwJn?iJ-g-3>;gX@oIc?SL z(Nf#wCWM?nQD3IA&%dkMHUafuLU(!*$!Pvmt@YtqtHg8x!(9Z0@3?T@tt`Q{eeAN@vvz ztWDCZEusXtOl6}`7!Xx!?VDV=wjsrW95+FGyJM#6fhkZJ&=fx||$ zKxa8FOF+^pqtIdNajS*0T$$Nll-pIA6L4wC4=R|X;K}z(2LtUI@^z?T#EDR61D3{)SO0Msa3jf$3K>abx6?K-S$Kq`l!!S z)S~wa7DVz$727;;#O!QxX+A!EYb#^>ckwRTywb>=q|aA?;l8)t*I@i=nu(k1Xl`;$Ek`tOx%*elzlgz-Ns=4; z$Z{remlfH*+!CZ_Xg*ryA_aT9dAZ+usa37?zyt=r5@5+j%-R zCiWaNx!YaL-1wWP(MWR!|L|)(zGJ`KrURM4$*Y8%y}W7)tXuE)KrdSoGpZls)5$x7 zU*|O@oso-ScGpHnhYvs*y=~0(bg*Pl;>tegv6UtigERMnF%0`0`75IX=rkzrGfV~` z4}xY*0^`|4MCf=>om&hmM=jY^Tcf^pF#Pq7tdT@0r&L1P9+@d79+TthLp4X7B&3d z9@2!{>+3)35m#(AnNiJtnns}~~yEKD*z8Cl}zEp3z>##S*+fYNTs7y(fhcS;TjzhZLAXb2M0 zV9t*e@oYUfng}qOGAUkqwQdOFT{Q$hgckb(b%2}-55h2Lvx!PTGy!bYh;Kv_OZN8m z;XnXl(qSO^K1c|+ryVGI8}T32eAKx8%g45yid8w7>&YESu9FE!dE~tMt!~`3#6HgR zw;NP4a752FS=5jy07uztCxC{TzO&>KDTYe&?(l1X%Js}e0n4U0I%hjM)}G#njp=|7 z!@WirB<(1>)XqIl0F}uuZm17C&wVD}?ZAM+Rg=R&Z7-Vj;e%(alib|V%p_yGmAfkj z+jjsILS3C2^AsxjcN!E-<7bUrU5h{IiKzH`dN}9Tb%Ri2;;Y!Uq!!K4F+%Hn`UNU+ zdTU^>eu~gzK1CwArgtEJJi?#BjSY>qUM|5L=R>o9#G{R^!f#IwM4 z?SBEd|Daw6aWZ;O4#Hsv@j|dBdIjkC&0omeK`8J4;!gjMh}&M>-7F2$4_*6(#HNkY8_N4CWfhj($iLur20hL#18)H1vl<{Y%Cq;hVpsc4d) zucZ3WK2C29DUsod$LFs#j~zih*#TZQi1&UpE5Gh$wt$rt=9IG=xIQ?Z-CqDW-TiJg zFXZr)uY4rt;kpDIN1X7!dj`;EI^rhk`#tM&02g;hxzFFX?iSi55coz1de=vEd$8!s zTn{(PS2`~18R2{r-3dO$s3%{{2a}&=)B^JGFV?C3Y;+uNC(QFF8~1~;^>nqunhzB$ ziGGEPB>*r#mR$V<;TYz?yY-Y(|6H+Uj5fOb5=fU6WU_V@L>s^bfPX~*vVjo}?2(}0W@!J@Y)8i&+ywAEY*C&ZyXmO@2E}+h} z<>Y59|HQ9}?Cf>}AhJne@cBsT&!Erqb0+I#CT_>wJro90sH>-kZ(I;faPnqwH~nEU-zqfVjDGGV)O{y%~~CYDwGO0ouh3k{P;PZw)G+n#!&)A5GrY)ySO4QnF+*o$91BGfYn| zzlybYW(zJY89A+91emwW04Zb-=aASXZ7qrVO`gQJQv0l+y95T|`(0Ui z9kuxT9B+p_pehQj2gi=;T#NT1$z)(_M8KF+4n6p~`bRmq9|Ur>=eo`{tE2Wm^6CHi ze_K=T`-W*|A?ecpy=8F|Ydf=qD=+c=I=uh&$QdaeH11J-+8C(9AdswcZJ;$ZooV8! z<5BvB#~FW=(|<#*M$@Yka6xn*^?_tP;qs^d|L_xxj*CH3{;+`nKZ4x)rH2lFK>Uwo zFaVVU1ajs9z4iw;i1|M^_&+xI|HThV&7#hL1%rMoRnqGXr#oYci*LED#hRm66rZ>3 z{)LhT;d-f3xx^Va$0PI$kCTq%=(Zmk`2_!KomhECMd#$v_CGtb=;R2lBI4f~SW3WH z>OKGhV|-Xsnh#L3#kb8i+FDtZTe)fO>Dr>U>R z$yo#^JMJVcvpbaRL@ACr&y6~ck^Fr8yr*7jORi;pkN~V$!phW@i-@GVIj@HKH3^vhOK|_#0-8(3h$}6OH)YXfKsHo?Xwh^y}4nrO)2}r;s$7F3=2QFJB z_;kJgB^=ke!d478^lOsyFGD#h-5RpGkh&O0<%tjm#)Y$C95;7orWH40%j*C$w>&ns zPyW-%Z&hx)_02znBF=)8Sg~z&?#p?2!+E&<+npaRj?FSp8i5yK$uck6X@HwC+u%c$88)IXh z-_GLD#DZ!EJ9e>VN$FE!&ARb-dIccmJ}dIW1?F$AUt}8m%c7m} z>Rbv>d(l7yus)<5Z-<46xF@={wGj;i3f=mR^YHA5jpfRY=Pf14aLK3ZG5X<-O*xW} z^-R$-K!ss)D*C6#=GQ?wr^oFwO)%JcAJxXwU5ND{MdNl9(Th23pB;Vo3O>Jjn>Ie? zJ~g&FDF}<em|2d%MS&W^@(%1_A0eN7H;a3%^K|% zd%Jr7-44f^L0u^F?izIb@Hx3>KUOm`GJ0%xH+*J*Q*J-iOFrEsDZ#*8a`&HU^p2!% zY^bo>S^Lgpg`=kO`z?3#+bxQn&BMU#MX~TmPPOPR_vT(}Ssao0R=KIbbk3O@z z#aA@-nj1$WtjLHxP_IuME5{2i83R`O>6?7Xpe%I=L_XW2xoFaqPs(hR;8Sl^KZW`mNQ=WXz1i4q+I2Z_b z#8FOH#{x>8<5{lyMO!w=Elmj;s%X?_yu9oh^o#mUkX+?kbIE;&0(T&+_)Sbgk@%xS z#rR~c0dpdw&(dKrkr%vD&fRMs5`g{x$GIFFsm#1LmfFF5*TO{lmJ8T2v^y$OI??t1 zX8(*~B-cg0mDuqC9P&fFl(Bfvc z?Tj%p5Ic8NCCq`kP{xx1U2LB<{T{L=BC_QA^W%Brw57ak!jbo)3-m`K1+R`* z?M35aE5=?TnX_`?)O1?ar>}6<@2^-pMDi59zM%(}53C1&%$ExilwHd5PjgF}8Q}ii zhL5()5XN=sP{(A#@j2<|i?hA}LwTXP=(wI@JbvKWS#}tM_Fw}v0Zq#++-Oj3p|5!5 z5Uw73sp&V7K^x~57Z%3H9CkxYiRkS|wC)lps9OICGLPRhfjoRzFdx@N^qFF*Nms9r zIm#h^D6!d{cyu~Dxi+N;ARcFx!yHU@lXyI+mVM)noqrry7M+28pd=YD9bMm|OoBkX z^*SQDg3YP*aLvl5PhzY4^wDWqDeFpe)V?Z*iz_}#J)w*`m(%(y3`kr8>@cc#><>Y9 z9oC>N>ce*BJwzY1HRw$DHODIntFdc|ts4`@Z_FiUfe$R^fB5oJF>zT?n@8k;$KD!W zYg3mTU-&xKPmUOi)Q>3skZq%i#kG1g1C7Z)@yriD`Q>H!b`K~tZ<@W+nf2LHG~^hS zTWPezRrY=WarfyP`~Sp{TSTes3{xC#qjwaQ?kEt~2oy@6`1S+Xi4x}jxX_Q8oZMVA z73`V5--6!dX|OrbD<8}1uKBP_V7&*}_3nGxk3`E*PN~VGf~qA|mD6*~_e_1YGI)+EGn~v@Xy_$q10*~rN*?WRPASpS zpct2=hO%9n4}pcl=b{G{+~0lqM&Ba2|FWfb5SUS`g1s?K6X!oJCBDsdq>%VZMi&N! zH^M{?cFn6A-^QJAPSwit`wuZz%f0>!EYSb@oN#UnnyE1L{ho|?CaAtX+l^O!nY1|2 zZTZ~ds;b>|PzO??BKYT9TTRzT;DdFTkKZin!4xqKX6=X0&NL4`fGAxQRPH{*so>H} zEb+5@`K9$>pWAQxEc-MwYJ_~eRx+n>ME?D;D)>*R%bPZ^)kDR+thKyxXl)IT^O|0%QiL#fRd} zXT_S8_tO*qBr$UUOa>>MR@E~(WTfac(Q(f9fFS^oaVCb2Ej-lIqcH}?fpw(xpZsY( zr0obR=k5{u&B5N{IxSLf>X!9i25@5gZN9q|ATR8t6g%wgwuAO->RgD6v(R|`G53b; z`at6#O0w^R<$;+R%`8UyQM%fu36-ez7-w8=K>>;`JCYT9r_tdTE~iZ&E`sIeL4)vW ztAw0$hXY_k%45uawtHWP#gY$qC^eo0+9%himlBiKpw&-z>Xn$wZ>}Gl3WS6dNQljv zJI&Nm=k;WU9s8XHPiyS7?tlsIDc#ZSp~fwvOV9mrz&4%k9);Y}Khn*^X}#SQ=SoMu z*0awV=?u8f)`3Rat{tBRk4Z>Sbf4`_91i?&z;bTSI`XMh%I;A`0Wy^Q4wS0JBH270rKPpuO`x81x64mD&P=vZFm zQOX{x=gv#nS=p|)ys9JCbX5B^K;B5Qro$j2OEEFq?!Nlcb6_I-JN+5lk<_)L$=-vi zVe5D5eEZ6enKy+&O%5f{yz_1kf?=BGv>pP%^B<>^xMAMVEQW6Ds5v!X;5$8xJOjeN zcExTKUnGU9OB`I;u;WXd5tTU5q^}o;9+jUnlAeq`y5vRaOz!h(ASEiY{zqb zO8BJWUbQ?_bG+5XT}qcs0n4u$BBwZ`f0bh9C5x-%9Wc3DYbOB7)b_r$mKWGUP)py% zgB6F_fhI$S(8M$WWWplrpgxlb{O>lsU|l5T{>c`hBLSH{HE#X0&2-%;5JRL3=0;R82$e>PYC1&c!6Uk{2XfhJ(a5nUz1MiiZs4Rfp{t z_l*61xnCnyoam`2K>`Jg{xDC`23D4ueTv&35E4Lw<_e94%ROJ#gTu)vvSY%V3;|nMraaGKolsf=SBk zj3A-#O7aZrj_6u+z?F+ywAhbf@`0A?XQF-ja>LeCWw#_DBcg)`IY{XkBr3L;A>NGk zxM$=G_{s@%cg=-mJe5Og!s@0NU#oII5TOg6iM#ScYi755v$EnJA=J2!NZGJ7uYsX08F9b)W2_+O`(cZ3jj1G-Mgl%du%l~a_-VX-aB`~37vaQnna@{% zSW-mt`xK%pPfZ=e;SWUWR|=Rt`{$t7lB|<=2amrt4SwztxBO0{e%e{gM}QWD^!5Ak z^W+P6fqYZCST#8L4nCz~{i6a`D5<7PIz-7$&Z;J=EMNL)xRQr26eU@AmR5@$J@Gvz z+;8>8(v~9rFRKWg!n;{XLZ+p|&2fTEIgeP=Ud}`ZSUWO`_SFLpD<_V9*(RnT5bULo zwHS*=NIZOyCR!nWtH-VmFz8Xk?Q|-QBppt~qYD=#@H>xuIR=L!k2^b@?r^7C=tboR zIIl!1j(-zukDemu<&GnnZ_yKAfQ*th$Y6$_O|vaai`g$87K{7Cc?#}b*&1EA+h46= z-E-74p(fg7m)kU4z+~RSsq44aB2LeudR*i47R@V z3Z9{^Y29qwWC;43IeU1+MO9BqFrMto>{jm;k^h*K_1V1fPWTaI%6jgKvc9|M+c#S z!n0z^S*%nVXQ#A_^e)&&`cAxwy83G9wEA;ceJE!G>BH~6FUW`>@3R5KEIxY)<{sgs zZc9s`H`(gR`_OIi+Y~09c-L6inB~F((2J6(Hk<+0r!&-!k*5G(lFbdH?SuquD5@>Z*@{x z8@GNtVEf6Xf}~e*Z*6a%+j}IaR?gm$T#)gqDu$QM{nCPJjt(hCTB-%dm#xq^6kzl6 zY0g*DSQ#ci%X$9xq^BKqZrX=*;eK6z6IumGa?|mRu2Jp0;WWjdEm3)au>en~E@q_+ zz`UK0O0mfb)8UaL5MktMRSwgW-9hX-ltSVI{Q1g}uUeH6OR(RX(SM93+2=1a*GFx6 zo2X6`{}5hb8Id>n=3*Gjuw_VqwDRG1IICtB)T$a3xh!mi{H;^dpF89{nz7Z@X<0oa zmKD0(5E54=VuR{eKOwiK$j#UhL2YIt&pbK~FUZ%UIW~qt6Z*WN+JCpb2mw7p&Qq0A(M^~zLnalYrufG&)6x|k&Hyu#jb5?!A2&LDKYHjLOvJS_q3 zt6oPn9D$OINE_RR)XJ%VQvjY%5m)DyHJd8s@}B_`#Gi&DHu?4aB4`ANEeh%yj0-yU zhY0>4eZ9i%Bmf+Z!O zLq12HP_@0jrROaxf&9GAL`GYTsX+%ZXiSTAaSlYpp|vxU}V9WPF7&JmFaZPNAW|DlGrh81*^ z&PiN0KJ%8ESoq7M+gbsl`AUW>0ljYGDP+Txd8CMO(q-GHy>H2Rm0998FI|9;xt)05 z>H!{`YBE=T1|89DX$yMBJb0IL#W<9YQLgGu&P_ zEq%AlW|{ypane!8gmsL^yg@=%I;Rj~2=V|i`H@p1J&03s=hN!Y2!y;KG43b1N8xK* zb!iFuVLBvx>zzj1S|u&97%K@`D}}E{ADZLq>Ay@ByXC0KSY*U=KY;8KFvBdBq%~sc z!xV1Ak5Tm0NMPPBx`twJI|X7as-%(TFxTu~B7YqSi*nZB-({ zw6wKg;Wm;X3~*at>K?PEW}i`ni#Uhqx&IWY@2arXye)65i$JVL7T8BC7W5dt%sv`r zqHQN-%C?61Era`y_wbzamSZ|V;n{?a?oXH2d|ilMZ|I>;&S`5G0O>EfPij7YQ)^2=Lk&b)8ekMRcK^YfYOkI#Y_n(&mlepWcpYR51t zGvU2Qyk{~xVy0X8$YYA%aw7%{)R9^fWb^K)+cQiV=@K~@rYAnO_wMI?)$4PKwf7N; zzm7aQnX_`JCIg)9z}#T|riLT*8x|lwov8OKNr;?L0SF^aXU?!tt?Oqse@kY`(ik^F zTgD;P`7|DWztuaK_0)ZW54=asAGljIM|t_D)1ehv7iPlxT!LO2<(zTOYdUgrXZfRn zgR_E8ff7f!X0;NILW9Z6t!vE(!sO zsuQ$1Ypeo-%~&$n3A^pnEzMisv`Xo(eZ_@s%THTPd9Ak9cgJ>;CS825qs8A;^j1ho zFaSAy%8dtnVz%qzetw;&lX>Tk%+9Q2#iH$~J+mD3bHv#wjf$y{611;&Ix8!QI}up7 zxfU13A1;ivZ9^Nj3rXT8+9Wg|ZC zxI$(Wn(vje0@ck9=PP^TLnA}jZ;9x;j4)aqe(ik$CT4}2gw@^^pC?NgDWKsU+C5`Vbqlu-0H#|N!04~bJ`?Zosp)Dk; zy{<)3!1DEblPSJ{Iu1JzT}Dx+5HbI-<))My5=@40IE*GUg^8>leg-EOz`-+F}i4*UK zuWdv4MKGbeox18FRVO#n)&tc6-OoYNK1(Z1+}Xc>+3H9rDm1%m7JYPVQWa=x)gD6XSTLbQck#3ewoj7cMKE9v)8f4yIhrvk}H*tTrSR=#W+?<(`>afFe% z{3^F_r)Cp&CG3;iiFeZoQcQ2&z0j}vpU>Atah;uEswxgM&^aPony(;f#cxhvi_8O(QN8WzB2K{xf#bDZi>At|*h8V=J9lIV+Li97A# zyq+?{n#t~2pswr((kiWqZhS+ENSvG4t&oNjZ(!B-zKtyNJbH)%4+vzy;LDR?`#J+QML8=u-HzW%FA%W>ci3|jwtoI z0p0nkZ}#@rY;~z~z#R5p2}9CInf0vc*P)H;F;vv7~Eb(PY=Vu3}NQ9>zob z3vQGGBCcbLvKw0ttNmy+^$e!rY0jjLbliJmKqCj(@zs7dvwbs4IK|k}tp;C1?ugX7 zVFjb4UoqHLZj{Qri`meUfnUZk>DrWDWFm{XW{mhmBpVFG_t)P5tX=<0Z=I^MR!D(` zdkKj5CZ~qqvI?7L7|$Q!N*6zw#tRgr%YL}24Xit1!#YD*KG;5TY`Jp1GWF;n+)6GN z_@!4}$=6vp<+Ey0S(NmnFwJk()kmeD{U|cM8!>cYzhB}vQ=LtQe%wbW&JdWEvK!KE zXZj}=Tg%+>lP*D=p~V?`dUXPHG`qjxJGcKBz2NNk2sJ>JEQeK-0bo?tcR1vwa#e3( zuIpR}AN>7tH1t_E@)ek4%ttl1X|4W>(~{Ig2Z&bHZ+@EXheFims$3+Wf@mtnC}4TH zDNdh`rKU1u0s7aNaM;&rZ(ba=S|1T?%pz>T7M9v8q^Rjma2Plw1n z_U5PatWWIw6J}OE0D5Hg2#&m*oVa}tA;tRYZgsT_H+0!}{rq*v895R2wKp<&9fxc(K|b4?)H@e;5b)c1DG8HQ@S&d@T#|7epW1zR zgvaXv=|=IGci@frSXoBAMSyYF`MQPKYFfGN%n+LGzuSLhVzI4QZZ=okwA@vs>Mt(8 z??^BmuxUzae5sjdApy?)BD*7l@oGekkfu3PPvoJov0n?$_-AsV^x)gD?{tW!Kemj@C6CuD*zM;76ziTEmRkdP=^*?@ z;20{zZuSA<-L-_DrycOv$ANP6r!8e^ehx(%1ez&o-j3A)Y}go)ydfDwh6~D+7)q@G zN#^Ifu7%4#Mp4I)@S@iFY;6%}y_piz{jvW|*i?vLX(~zX znMsSs%j}9)0Gd=uZZzLcG};dXf=VBKJk{Uw6hZg? zCRtO4g{b1(eBt}`yX@&{~s#K zy9ruYwUeX>AX^C=P#uADyZN$v zS_~VHL833s3MX{pT<=d0O1+Y1dF#6Dhi`WO)knRZSzLn0W?{mQ=7o01xluW)df;2& zm4EZioj!@{Nz0kEk)W_V4f-)rv(F8xYUh-}3i_MuzD1O9pGZ-n&D~Ma-4dzi)*M{@ z<$v6x&vIwFoVe3?T*KpI+D#zqcyWEal=Az3f2-OLMNgO}pQ*^irCB9E9$2XrFL3+y z;5^h0f214QW4tigtL138+okFyD)sZnP)V8Y3xLS_hfiEbnGV~}qF7}o-)_zVI*=W3 zDk2@5iEml>4w`C~}$>RaQ7@0-jNk zj@$pPuHN<&HW4CN0e_^oF0<%5`Z zWp7VP=ER8-9y%iVmrf;59!M7NbHcJFxsRYGeh?C*-R;{lo{cxBwoHZv3GVQxQS|JB8mkfE1N8RH3_ zyH?{ar}`>`;&UJ9BWv-th`kPO1>5ytQ}R%sqlYg4&jM5O%Fc3T$F?dkZ}8ywPi+tL zgEzX~Mwi>7`_aBOcO7k$-23t>SHAVy$kx<$&BpVMtnzl86HP%;=`2f_+@?8{u0r8K z)zLYY?e0h^h2j~>VM-pIYhAht4aFsnvV(>4cPzFJR|h-c_98uMpAi&?J64<1`MJeX zL30rJoj!ahwlV1YRt?21`n9j_>^=`j;uTF<*|5hZQ@I+v^3B-Knp~ zL>cKPB7T5pmxjrH5Vgp)cQ9$(<0;)@=~!(PCJ}f#vxF&3eAS5H>+Srh8`P|+svWZVIaHshwt}mTe{e+-&*a8&`_+lsK%%V9H9yCZIz6n4taq0G#n2IJmFWe zz(*t>KiDHA*K>a5@Jbw*?7`=OXzLxid_;eA(5q`bI61tUA}Ps?@>#S;l6$&JHOY5k z?Il^k?nLmkbF5pZYm>W|FdCTPgMEyAqNT0fV*~Voin@wUohA0jp_vUj{WRI4=a)-= z&&65pr|HZkU;#gxJnmX>wPBi0isnUbZSHd0lgWTXs`e%Zu$myx49Aak_LhC8zkr62 z=t*bS=;wC|d)J9H=jvY$4C7&tvgOSuKRZ9Lhdc1@txcvWEB4V@*C@7k|Es<43~MU; z_6{gIgV^Z^I67+R29#cG$S5EL5C|;@LR6X*fza(pizr1pf`CXT(o0YXQCbv)&_OT+ z=^^yAdrll@=6UaZ-uM3B&%>v1_CD*Zy~=Ozv-aBSIDjd4ZNQ!&yPEaQP{_4$_3bQa z$bYiAyhO$KwnjtoufkYiAlteiadyWZhEW~YKgc#E%P4a>9>3;sgN2X2d}}9P^~Vez zjtT$B(#WJ4h%5T^M@lj%lNKvqM(!>G=UT2!yjsmD$j`5N*=DE%96iXr*+Q(iYiXDw`_r54A&TV!} z_Fz4k19Cr7BOoIlgG;g5A)-p$I>1`09t|X=_f0>TX+-MK`aCl>NJtAEa`E_6xGiRT zZi_PMZ9{}naVaSDn_J{s8%Ua*vp5o7u{?!3SU&fZoFhH^8=E~hvbXxGQ<}n3{L?J% zd-o3U#69EZ;TI4Pm*s!H!|c@QQ^}?69ZnY)db?pVbyfl5Z4B?ug6Z)GO8vToh>IyN z%0({gMeB**?0C0BPh2j?=Lp+Lx^{7EKG@D$cKwm}hIcr{~b=6Xe{S8n`v%1tORpJPuXy5jJ{-fAI zryUc|wkeAOV+%X(b;;cNfM%O9MC_(RI*Qhz3{+}^d`fkmLOe&6zl1R8U}E%$X~#HP z9cy~xlPB!H|5fK5FyP|bD4*RW1&X94Zqdly+BWCG8?82k=mB;9+z%$?Cs=ymt#4m% z>tBXRcwA1WEO(sbJh{Q3o#vYUd^gXE zr8jnv&G*6{3Utm46iq~|?bL$X?6D6quRGNAOEZTrYnu%=OLLc!5bJUJVP!6>K?yN# zpyABk{l}V~Y5Gw_uB&DaiN>Ow(p?lE>HvgR{-Bjxshw;yK2xOxE0EjFaN&?grNsC8 zZcn=o%^Z61ImFHg2J&*|MO?4%FX?X3OtNhp<%y;vA6N;~n7=*DQEf@{JeOR)QMxW5 z_FE}T;5wxKyz4LJvITDY9IA|Oh{HGVx!RFftNq~dh)>3I{Iw^E%p}BxPrMFm?y{Q= zAB@3!(ma9D$d9X@=Q4EhNRXu}>Zh~L}Pw?Bn8982lyxTpo*<7_S~u|s&zv=_WZ zClwWv8xE$nS3ZS`H$FoI{9@Z_5(NC8so&XkE9HKF5TvsH`4F+{<|EMTr!POJ6@FCe zF>Vs{K5L$`snW$^*}D0)X2G(J1JoWHL0;QM8_vkV91R!*uiBa&^astc7)iBoWW%v0QT- zA2_~G5n1DwgA#Q~?-%jTYJ16ur&1pP`eA}XCfN;r4xthokXKT36>Sa-0A8vj>wCdVROju7S=0QMQvtTv6sf z6^q4MscF6ASb=M;XOdIGsmGS-+B(GMB=UE#W6IUR8`rKmXY*9<5&HsOaJ>evmHmj7 zB_3`*EA*!u@|*%1ngmr83gThFm~HJH%&qTf6F5|T(h%tMcBPNTr97RkI$gOOpa?5g zkt&$qr#S>%wE_IF!@v}v5LP7JmSnj{lW1I;wDJ)P!kZgS1bk>^dH=rOm3KVZeQvI+ zEjRqVLIe&9p$mg2pTeg0YhdL&yhEM)1jSA`KW}|D7Rj-)GcWJ|tF7IW{G*s4)tKySbt9lF?gUo~k;_>*8m)xKjyi}FDe zV(g=Ay_BcD36C<|I8v|87(lpr?@Q5IF;E)RVgK;SOnP!E$}m0YQI=lK;et*E;6VTp zNG%SIHYeA#82bd4F#_S~^yRlAobjd~ZOg2q$#%_g583X%#g6izi2%n6Au9&XkMAIz z(V&|zDU%-0+D)iqh~y+E4z)iV9`{3|=k^T^&aH5mKYy@%(4eX2Q5LzyYoX*DBI!@u z$&B5PmgrK_DTu#(3fDjGcq!cp4S)^Ek@D}nIXUxXfv(<>oz@^Wnf8MZ`}8aPxb5@a zgt(ZKhYW89nOm0!H*!R3@7VeA*0JY?$CBGuKpz0G59r>MoBRxf;A_z2bASUbd!+v8 z>@D&fz+)kih@#^OP6{2jNDLPxj?KHe!_`L;QSfx_N7?%Lg~qaWkGVA$M?sLK$`??U zAf-k_X;)D}6yQ;19qn@~z^Pcs8KEaRO=Z9P$Gz;&I_^|4%k1P#f)JmIBy``mBv)u< zI07)P_)8oo2`h4P05G`&#-DOkc0Jk&=WGd%th6_}Co2nmqw!}S zfzl3h-jDB1Loa-oGpQ?cFpa0vWIOotfd0B5t zG{5je(OOB80gg zyIef9Je-Cy-YXdyi#DICkv0wINWatO8R}{}Tx*4C)NxBci=qm`9AvuK8N}dM5^Iep z&a{6Xyc)LPZel+4o4@Z~G!90iJ}IPh1%XHYGIvHO$`(IVQJ8Hcy`O!K@-(koyOx3h z#UGaLdprAQqHc}Bq7c_7W?Yw0j8;=*b-BNt7 z_Y2s6R*1wM<-*-s!IcvHc)Y*<`pv+tt!c0&(=p2+a^$XK;y9P#mZ;u0K-i4J&B)7O z+YNR2o8V&waz*03*GuGzX+rw(l&u$Fi+xWAI3pUkJ|i8}V6p zHBw1~396z=PzXyEoT||oTE?0N8@HdXnfmEqWKfNZ29i z_pL^W2szIwp52*aIgZb8+=h7}(*L?cY=Fh=N2v0)JYJUl&?mPKlyTVc1LTp@Xo*gm z0-7a5e72*Cc`3aY9rCm{qRD$VSnLw=_L0Z8&8z8`(@C@_g<$p$}yk_XN4`5^F z>6SZ8yG~l9rVD_a8tibQ-ko{MC2 z5jQMyOt&O_`(x>bI-5sLQ6WKVNrW{2wqr`#$}aCl zcS6#rGtcM%u$Z#-G<2dqTQt|NA%t$4kn`CA72mNlNvGqTbnM^=pP~79efhdKij>fxax4A? zorTP{u)EvFg4QdC8V4$e78K?*0_KeNsI7$0KH4)fSskRgWAig$#9`dV7r6T0?1&bI zO+;-YNS6D5Y0M7ot3t9#S-+%cI^C-H7AX=4f1&Hy$ICO8>r6H!UR_{Nc1l|yaivy! zt}vQD$2SJki^QvGT_yD%i*TI*KOBrS|F)+qZ)&oSgc18W(p(@*n%%r67TAQGFQbGb zU4hd;9#@hAg6_@j$bTXzBVn`dYbm54VI(hDBVmk|<| ze-kI`0p*7vyl3|FjQ4#BTpIsPX>AnsMQ3&BW|N2Bg4QiJeuU?Iy*Z+GC`$i{qX<!Hn% z3xFeJLiphj_qfK>1`hx^q3WrR{;`Gh%SGa zc;$$1uyg{e@?rwhUpL2ton}YP24RmUGn~8Bl)_w9kuxu?IHMg(O|1s+Vk}gj*2n#&=z=JU$A!J_RUm>czG_HrWtB5FD}3 zt)N2}vkS&l$1~4|Fu!6_7Xrcz69m{STHF4EI!mqJqzGE+vT`by(}T72X-Y3Y8C_h8ABqVkQ8b?Zli5e;qAQkN)>EY zmKUmPL!z{%uq(?Kr^|3+Ft5eL66=+s3b_EApTbg}MN4A2N;cY4kZ7XaQRHCg@Yxc* z-vmf6O}pG`3Im)C$!#t|W}~#x*pe6#zolI&vW?lj7@40kF;`tIu_@iCV75vuIpA0t z4Xm%2pp^>fw(LTaLAQX3fJo5Yyyo5>v>UueX!MBE37XK~<<4~XQ~Qf=>+kjK$7GDT zeC-^&QO%w4Kq$LVeddK!$p!Rk55Yo)rXCBJ8e(&qU9-@6e{WIrWJ@+CnA~09Q7CIo zz3!*+H>K5jOu6*sf+u>xOJwcfN~DI*hD3>h1~qQXhC!Ao5DT}IXV9G*fU5QwY4+@d zLPJ1DoDO5P`23*VNSVi2g=BKb%q&o{$eWxVUrgl3OYHnCo2uBXw%TT4Iw!ut|3hO<- zOsixaiXy@M{}?JLqb-4E>^=8o(jsH|vrfPihLXNIhqUqREGf3$SVfKn(ATA7K_~}t zV61ETvE;1rU7;$OQ3Z(;5{n5Q3c>TqV(tN~NLul0?Xf3$sjQCAge$h z*}6u-k`fPGh{uA4?{ErynfVcsQQS2{Pw`-zJ}w!{``d%cD9mk`l6(fU0b|z4N|U_m zaAG*Jq`uK>RH)Hrv&Sg7*z0}1l8m0ATCpN)>1e?~aKA+S$fJ2`-_wytjr&Ea+cPWX zCmTlj1}og$h8x}1uenUZ9%1|!hcF^e-!A15-)N7agx}L>hZ~x(}*Pf#sLp<|;{D$!V)G{0rG5IIVWL@Nf z@}2>m=s%EG=Qz)(9p%BX|-cFQ62e$ zkFB>>b0*AyT-`t2_yCW6L)Lt82CLqRuXSCaVXIYMeBFP+-!M&1Vv^~j3)BPDm*!{} z1XSxvNYrrYM$8Pb01EEYX!L9E4wMGLi#$8F`B@{Pb|X-8N=wGVqP@P_))TDmoiL+Z z8=B31vQcpT31b|nf=_LL%08Y`e!E3+l1aCrfW^IoXXWIs5Jf`m8fR)|4vba1Q9Zlq z5tKiO2iHx(Ddkf+4^UUEp&GR10^GF6wDR{wYP%OB_`DR`L4w3kcj(?J)2vrtrRbHx ze4{_t=reDamdxcdl-4ezgd!FtKwugtSahlys$_k&DgZ#$!I|niYa#I47i!y8Tc`&J zPq}Z?1j9TRTGk^euKd29X-#Wo`NzAeT!iEhc~Oxi1l-;_J_!@1cgbi&LC(lkmC@xj zlF7s(o}QQpA>j6V8or+ho}f4|7d$-Qc0?Ug# z=_eX3f{^%Ei&CIWZ+zrF8NZDlHHy2oKhfQkg%3S{sS0!M@MYYFRmbZm1dXMq&7>NZ zrC!7v=&nhj)j#S-tC0*9CbwjYh)>nBf-?-S8oZk8;S)f+2X$2s46bm#(s4ZU9_nNH zPJytLG6E>Hf!hEVV$;3 z=z&RJSo=u{1@{!*8ZRzke26BChX(9!&#%>mn5dB*u_Uw`%vzyq6F5BOtu8oGGwIu- z9b~l{dZ2}wEgM77aGiiJLLocn>h;N~M5I+iXxy{>xhO|jmRtlO2GA%LMv3JH)@xz8 zG7#))hu2LrNCIlJ$YaWUSlhWg9`#yWX|-ifXX&M!b8%L1zBm+5tHKPmJ!CU*Y%Q*y2>QuQoFtBcR{i()idh@hFe+t{se9t zd`4&&1KKe$OWnZJ^*#$8Ls}(UYhRqa(>e=PP>RX<2xqC(?y#37Mw6~CYF;Al=ebT@ z%r`awio&IgQSbaHRXijRMvZj$^d(No0$IgI9TK%8pHYk zg_)7M`h5m<)x+0wQ`iviDC>v0SN_@4LLSnC+fDT+lf;xzz)=+S2qHpWWs;f5)_cGa zy1NCWwJ`1N+e$|JR<2+etl&h#@M&q^i5kc6BU=(j-OTnKJDaU2+xF7yHv!#qRw1bo zUCRb7mX*TT$5|Jmk}kmZEM_oX&bZAA#N0%?Y5Hzi4a;ACzM@(7s?=s!7yhi+F)=|g z5vdQO+GV>+DPUquj`H@b*m$N?yNQnZ$M^(e{SEO+aQIRyU#Ql;IVsT7mawlvO{-H^h<@Ldq%Y`4^N=1!hc{uMTk{V#%L{@MopdC)VJXU)_6I{Y7C^kKY-7U6(4vgWxxVpVIx! zDrDI5*$ZV2Xd}*l&OgS#_>?pLoYii<~eur6l=lB~v3tt_n*+^2Bo^%ii<)887 z%bU#cmx$Zdq)M1j_7rcrSfbK-+l+(Hle?OrW+qe<8{Xm9kf?jIjTQ>zBZn`)IW;Tm zGNyW%HyawLZuPB2feJecmFbv!LK4H+rk{OLY$1ppYf}=nH`Qp?Pw)M zI>si*>cK5-a;;@?-GMPA(M6>O+^HDxfx_Rl+-sr?Q^ey`-iMpWHsoH^)zb{EU-aS< z%Q!o%JnB&fiCWH9bx~@_4Zxnv9)IEhV`??5PZTx@Ki|-d;IX%A6w$qozKA-6Tuq1} z@U^)r$!~MlNpPqf(?zF$q+R?fJS0u=ORt#c`L=st5p~4(5|?m>Q&ez+QypAB&(p&U z(t2PV8q}^7(t}Ei$ykPo5^Oh>q6y*U8t#c*BrmQP2@eyAW2XvEx?TUV5aP15WFhQ$ z>+^Z+%Y^?>3hX}6TNs>+*i5z&8~myah-ODbj)SJVw0L)U)5H`Pe&oSqVA;1SOGtu# zg^z8igIfGTigRzyIe9llV}p9qh8;gY-gEB5>QZ;^c6^c-e&7mxP4`;&4a!tNyG##L zRIVU;AOO#OQsACg?L~+BjJIOmTd86K56SBScqc}~=;~91`hH3QNe84txW*b}cx z<_0>SiNkf#0{ZScmD)Z<3bHiv3wWEV?d%$|)H}5eQ%X-sLwaCVjiITN_{ldxQMn>{ zWJh&5gb~3OjeWYo*Avq~o`j|i0dqm`0IV%?no?@FSHrXR{ejC&^gA$r zBa1)WL`uMz?DgB_yQiL?IA)8JZ@{gH&S-_iMNtR4$Fk2vCb@sF5JZM1CPkBo~g>I$t^0@y-ds2Kz!kAQ<5X{0D)y1a-c7bYr_Lw+@_>O z3^P<))UbxMs>N%Qpf|EVMV7@(s>M{9^{H(6WvV_`Efx~VxFcJGA)Z&}6IbH};l{6=UVpC*f*)$3k@Te# z`YeFY2t0td;ALygZpER5@gz|#Bdc>ZcD4Pn+@&@l0;sbW2a+O)h0I#F{Oz(r;-2&_ z!7%c`V`~4+Up?wEP?STZWyfuU_ERcmpK_d902{gd@}i@~V*kK79(%3(03K-Bd#A5^ zy0)sS3|_7JoU87Mds}80R|bC|kl0KBG%r|^hhW&*^uXfBR~IA+XEyKPHwAg@lLy4G zOA`U{c*I@!;Z{#!f<1LCQ?)hwtv+dY)W8GJDE>%l01*+SfOpika|uXy;K~PAu$k8U zVfdUT_;$heG=Nk1uv3nqifFtm+19(g9fIAVuN#S7ok(O)+?klsa*lh5Cm@hq!d20; zQ@Tm95?D)zSLA1_#d<>Pa_(l`N!%ypPRl3WhCgUI{wq1B_ht`fC8V9l9++YRvq!um zNg6I@kX#WfYWi$+u(=ht&Ef_WSF%z&>o$D=Sx{x8Zc8gVN-~*q*6noGSafRF1DD8W zdW1j@5!cWt6aaCaS%7Vx!A}%gKFbl&A-gp6%49^4Hf4z6k%T|$9_A)FZd)C9UecY_h>Ew{VQ$7Cm#xwTO6_vaBLaShTgc)z z<_`nx2@27GLvjIEGl2-U=R=FLnj(^X;1Jz+FNz5k9y^I%zs1?P44+6iaS_e$WE|%~ z?T5Py_*F+E0`pHrl|vJh@=UPR@Q;spo6N$Ub~~r#%*Fxld*@31-J1OuqLa9U7ggj^ z3N89{V+DDf>&Ku>2ZM>Vo2@pYG=I0U2Z{|C;XF%Gk@Y9^)_I5F2^N0Da#kSh=9*pH z8_%)o;2^nCQvg0pI)(4AR8t39t8+w>@q`#h*~`5^J@2 z=Zj!i8Wa+#e-~>wy#1AbTD3!#y+Iw-_d1~u`oQGoivuMJ5#`JBjqr;lo=aZv4c$9-6OgoYGhHcG_j zJN-Ls!W-%_y#Jw{C&CkWRq5oFTc|%&a+G&uIfjtGJxU}ACP>wuV15}IQMGfC_UxIw zm^AR3KV}w4qKETj{&@F=C|`1TWF@N~{VZQ8KrHd3o%N>G1N^Bi;0u7S zMjt7_oMFCHgn&x$YZWo9C4j`bf4;t1DZ^Y!NAK{ZXq9zT2Q{?((eX>6-|ps^@FRW6t~+uO%oSxEcRyBsRM~iu|t3KvhnK4ZI+@esOFqd@mdspdl~w z589q`_5F9Nd+q%ALvP;4RPV9x7%uR$eHU{a*dgvj2Hb{E1{s61KM?Q(qL8Fx%6Fbb zyG1Ll;*Eqm(~;{uyQiBEH9uyUiTH+r))PmG$@x z3TQar6)f4ThSi1p`Bb_89UySO%e`czvszPIr<>ukRXDSr%`dyqCVtEw<(Fyx_u~%= zgyGW)0qM$Hm5*}Y2EsaTQbuH}SL@<|AXU#-?voNSCU&>xssVn1*dq|Emxn?;EQ9n` zmGgpJeVdOpA#2xiF9YGa9AZ}T*Q29#T`|N}-ll$j-~)Nblpn)6Px@sa93lGfVfy8 zOUs$Ti2~W}yd9hp_G(!{S@<8aG?#}sVlma7Kp5brKX^#gwxJ8CHqV#3_O<7Y{yhK& ze|Hz#1R{#+e+CA_xo^7&hLr(ndNkmzSVb1nBj4mXk9+Y|NGzb=W^lrs*sS~Lt zatxD~{vBTq79fqPo(vI~Z5Y2cg0lr~^v1gVPmAQq{E4~T7x+(i1^#6jUsM-=_~nr^ z7vPiie&E={{_&gRCYh>#Dd4Y=)qg|S=AP;0IeBCOU_>rcpno_gCci@q8#y zju0qzlM?JK_Y{oPI+X;k(KU4505-vi>ub2SQBNTK`&+VufF3|Rb2ESy8@>MbmW9$5 zpXl(~EMO4R-Y(rO2j-t&Nm;GCtFhPW>t7ou;tqH^M6 zTlM+l^~0g#O&*jCQxBcopAS_rUmRIKL@iox^!T#qjtIaIOMfjN?^`tYl>icP#kba& zS&~LTXU>jF3cilMvgtn-!ZepW$to4f4qHa}1{d3u`p++EjnRTK=KX?BvH!^3_zWzB zeryZX;7xkIGq=`5T}v9)nVmq4$)sUV8u3m$7^Id))(5d>>ni72z^zp{1xfbDklz6d z17QN<`|vv$8E4IJxw4nxrZGryY)@9pL-cH0{G)iJ(F+7Cw>f1t@_@v}*{& ztrYt;$SAMBNJ2f8Jo|IRok!Zx2$nk}et4-!|{pPfgtPZ+MCs$a&#NBT<*W7G!cD!$heEz-9bFd*CU)KTW0J&u zhpAM%h5(%*7P?p}E-3MO1tR#MA_@FNW#+?INH5*%J+#756ZIUw^!?~oR7Kit6j@D! zsEZbyLOequ0@I;6*=%1e+uE@=?;S6S)MPR@r<^OifYMLZYb0P(=-foyZQbm6So|L%@|d;Kece literal 0 HcmV?d00001 diff --git a/examples/wordpress/images/nginx/Dockerfile b/examples/wordpress/images/nginx/Dockerfile new file mode 100644 index 000000000..ce14e868d --- /dev/null +++ b/examples/wordpress/images/nginx/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx +COPY default.conf /etc/nginx/conf.d/default.conf diff --git a/examples/wordpress/images/nginx/Makefile b/examples/wordpress/images/nginx/Makefile new file mode 100644 index 000000000..5071ca7ea --- /dev/null +++ b/examples/wordpress/images/nginx/Makefile @@ -0,0 +1,42 @@ +.PHONY: all build push clean + +<<<<<<< HEAD +<<<<<<< HEAD +DOCKER_REGISTRY = gcr.io +PREFIX = $(DOCKER_REGISTRY)/$(PROJECT) +======= +PREFIX = gcr.io/$(PROJECT) +>>>>>>> 320a0e9... Adds Wordpress example +======= +DOCKER_REGISTRY = gcr.io +PREFIX = $(DOCKER_REGISTRY)/$(PROJECT) +>>>>>>> a893ea4... Updates Makefile to use generic Docker registry +IMAGE = nginx +TAG = latest + +DIR = . + +all: build + +build: + docker build -t $(PREFIX)/$(IMAGE):$(TAG) $(DIR) + +push: build +<<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> a893ea4... Updates Makefile to use generic Docker registry +ifeq ($(DOCKER_REGISTRY),gcr.io) + gcloud docker push $(PREFIX)/$(IMAGE):$(TAG) +else + docker push $(PREFIX)/$(IMAGE):$(TAG) +endif +<<<<<<< HEAD +======= + gcloud docker push $(PREFIX)/$(IMAGE):$(TAG) +>>>>>>> 320a0e9... Adds Wordpress example +======= +>>>>>>> a893ea4... Updates Makefile to use generic Docker registry + +clean: + docker rmi $(PREFIX)/$(IMAGE):$(TAG) diff --git a/examples/wordpress/images/nginx/default.conf b/examples/wordpress/images/nginx/default.conf new file mode 100644 index 000000000..af2d7ebf8 --- /dev/null +++ b/examples/wordpress/images/nginx/default.conf @@ -0,0 +1,48 @@ +upstream phpcgi { + server wordpress-php:9000; +} + +server { + listen 80 ; + + root /var/www/html; + index index.php index.html index.htm; + + server_name localhost; + + location / { + try_files $uri $uri/ =404; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + + # With php5-cgi alone: + fastcgi_pass phpcgi; + + # With php5-fpm: + fastcgi_index index.php; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param DOCUMENT_URI $document_uri; + fastcgi_param DOCUMENT_ROOT $document_root; + fastcgi_param SERVER_PROTOCOL $server_protocol; + + fastcgi_param GATEWAY_INTERFACE CGI/1.1; + fastcgi_param SERVER_SOFTWARE nginx; + + fastcgi_param REMOTE_ADDR $remote_addr; + fastcgi_param REMOTE_PORT $remote_port; + fastcgi_param SERVER_ADDR $server_addr; + fastcgi_param SERVER_PORT $server_port; + fastcgi_param SERVER_NAME $server_name; + #include fastcgi_params; + } +} diff --git a/examples/wordpress/wordpress-resources.yaml b/examples/wordpress/wordpress-resources.yaml new file mode 100644 index 000000000..cc8d40bb4 --- /dev/null +++ b/examples/wordpress/wordpress-resources.yaml @@ -0,0 +1,19 @@ +<<<<<<< HEAD +<<<<<<< HEAD +# Google Cloud Deployment Manager template +======= +>>>>>>> 320a0e9... Adds Wordpress example +======= +# Google Cloud Deployment Manager template +>>>>>>> 2df5f72... Improves readability +resources: +- name: nfs-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 +- name: mysql-disk + type: compute.v1.disk + properties: + zone: us-central1-b + sizeGb: 200 diff --git a/examples/wordpress/wordpress.jinja b/examples/wordpress/wordpress.jinja new file mode 100644 index 000000000..8ac1c3c28 --- /dev/null +++ b/examples/wordpress/wordpress.jinja @@ -0,0 +1,185 @@ +<<<<<<< HEAD +<<<<<<< HEAD +{% set PROPERTIES = properties or {} %} +{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %} +======= +{% set PROJECT = properties['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = properties['nfs-server'] or {} %} +>>>>>>> 320a0e9... Adds Wordpress example +======= +{% set PROPERTIES = properties or {} %} +{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %} +{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %} +>>>>>>> f7940f1... Updates Wordpress example +{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %} +{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %} +{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %} +{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %} +<<<<<<< HEAD +<<<<<<< HEAD +{% set NGINX = PROPERTIES['nginx'] or {} %} +{% set NGINX_PORT = 80 %} +{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} +{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %} +{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} +{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} +{% set MYSQL = PROPERTIES['mysql'] or {} %} +======= +{% set NGINX = properties['nginx'] or {} %} +======= +{% set NGINX = PROPERTIES['nginx'] or {} %} +>>>>>>> f7940f1... Updates Wordpress example +{% set NGINX_PORT = 80 %} +{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %} +{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %} +{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %} +{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %} +<<<<<<< HEAD +{% set MYSQL = properties['mysql'] or {} %} +>>>>>>> 320a0e9... Adds Wordpress example +======= +{% set MYSQL = PROPERTIES['mysql'] or {} %} +>>>>>>> f7940f1... Updates Wordpress example +{% set MYSQL_PORT = MYSQL['port'] or 3306 %} +{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %} +{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %} +{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %} +<<<<<<< HEAD +<<<<<<< HEAD + +<<<<<<< HEAD +resources: +- name: nfs + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja + properties: + ip: {{ NFS_SERVER_IP }} + port: {{ NFS_SERVER_PORT }} + disk: {{ NFS_SERVER_DISK }} + fstype: {{NFS_SERVER_DISK_FSTYPE }} +- name: nginx + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +======= +======= + +>>>>>>> 2df5f72... Improves readability +imports: +- path: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + +======= +>>>>>>> 3ed2616... Creates an NFS type +resources: +- name: nfs + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/nfs/v1/nfs.jinja + properties: + ip: {{ NFS_SERVER_IP }} + port: {{ NFS_SERVER_PORT }} + disk: {{ NFS_SERVER_DISK }} + fstype: {{NFS_SERVER_DISK_FSTYPE }} +- name: nginx +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> 320a0e9... Adds Wordpress example +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> ebf3a33... Change hardcoded type urls from leendersr/deployment-manager to kubernetes/deployment-manager +======= + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> 65b3c7e... Changes the Wordpress template so that it uses PersistentVolumeClaims +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> be9b2a3... Changes absolute url from leendersr/deployment-manager back to kubernetes/deployment-manager + properties: + service_port: {{ NGINX_PORT }} + container_port: {{ NGINX_PORT }} + replicas: {{ NGINX_REPLICAS }} + external_service: true + image: gcr.io/{{ PROJECT }}/nginx:latest + volumes: + - mount_path: /var/www/html +<<<<<<< HEAD +<<<<<<< HEAD + persistentVolumeClaim: + claimName: nfs +- name: mysql +<<<<<<< HEAD + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +======= + nfs: + server: {{ NFS_SERVER_IP }} + path: / +- name: mysql +<<<<<<< HEAD + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> 320a0e9... Adds Wordpress example +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> ebf3a33... Change hardcoded type urls from leendersr/deployment-manager to kubernetes/deployment-manager +======= + persistentVolumeClaim: + claimName: nfs +- name: mysql + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> 65b3c7e... Changes the Wordpress template so that it uses PersistentVolumeClaims +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> be9b2a3... Changes absolute url from leendersr/deployment-manager back to kubernetes/deployment-manager + properties: + service_port: {{ MYSQL_PORT }} + container_port: {{ MYSQL_PORT }} + replicas: 1 + image: mysql:5.6 + env: + - name: MYSQL_ROOT_PASSWORD + value: {{ MYSQL_PASSWORD }} + volumes: + - mount_path: /var/lib/mysql + gcePersistentDisk: + pdName: {{ MYSQL_DISK }} + fsType: {{ MYSQL_DISK_FSTYPE }} +- name: wordpress-php +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +======= + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> 320a0e9... Adds Wordpress example +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v2/replicatedservice.py +>>>>>>> ebf3a33... Change hardcoded type urls from leendersr/deployment-manager to kubernetes/deployment-manager +======= + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> 65b3c7e... Changes the Wordpress template so that it uses PersistentVolumeClaims +======= + type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py +>>>>>>> be9b2a3... Changes absolute url from leendersr/deployment-manager back to kubernetes/deployment-manager + properties: + service_name: wordpress-php + service_port: {{ WORDPRESS_PHP_PORT }} + container_port: {{ WORDPRESS_PHP_PORT }} + replicas: 2 + image: wordpress:fpm + env: + - name: WORDPRESS_DB_PASSWORD + value: {{ MYSQL_PASSWORD }} + - name: WORDPRESS_DB_HOST + value: mysql-service + volumes: + - mount_path: /var/www/html +<<<<<<< HEAD +<<<<<<< HEAD + persistentVolumeClaim: + claimName: nfs +======= + nfs: + server: {{ NFS_SERVER_IP }} + path: / +>>>>>>> 320a0e9... Adds Wordpress example +======= + persistentVolumeClaim: + claimName: nfs +>>>>>>> 65b3c7e... Changes the Wordpress template so that it uses PersistentVolumeClaims diff --git a/examples/wordpress/wordpress.jinja.schema b/examples/wordpress/wordpress.jinja.schema new file mode 100644 index 000000000..50a142d27 --- /dev/null +++ b/examples/wordpress/wordpress.jinja.schema @@ -0,0 +1,81 @@ +info: + title: Wordpress + description: | +<<<<<<< HEAD +<<<<<<< HEAD + Defines a Wordpress website by defining four replicated services: an NFS service, an nginx service, a wordpress-php service, and a MySQL service. + + The nginx service and the Wordpress-php service both use NFS to share files. +======= + Defines a Wordpress website by defining four replicatedservices: an NFS service, an NGINX service, an Wordpress-PHP service, and a MySQL service. + + The NGINX service and the Wordpress-fpm service both use NFS to share files. +>>>>>>> 320a0e9... Adds Wordpress example +======= + Defines a Wordpress website by defining four replicated services: an NFS service, an nginx service, a wordpress-php service, and a MySQL service. + + The nginx service and the Wordpress-php service both use NFS to share files. +>>>>>>> f337d6c... Fix spelling + +properties: + project: + type: string + default: dm-k8s-testing + description: Project location to load the images from. + nfs-service: + type: object + properties: + ip: + type: string + default: 10.0.253.247 + description: The IP of the NFS service. + port: + type: int + default: 2049 + description: The port of the NFS service. + disk: + type: string + default: nfs-disk + description: The name of the persistent disk the NFS service uses. + fstype: + type: string + default: ext4 + description: The filesystem the disk of the NFS service uses. + nginx: + type: object + properties: + replicas: + type: int + default: 2 + description: The number of replicas for the nginx service. + wordpress-php: + type: object + properties: + replicas: + type: int + default: 2 + description: The number of replicas for the wordpress-php service. + port: + type: int + default: 9000 + description: The port the wordpress-php service runs on. + mysql: + type: object + properties: + port: + type: int + default: 3306 + description: The port the MySQL service runs on. + password: + type: string + default: mysql-password + description: The root password of the MySQL service. + disk: + type: string + default: mysql-disk + description: The name of the persistent disk the MySQL service uses. + fstype: + type: string + default: ext4 + description: The filesystem the disk of the MySQL service uses. + diff --git a/examples/wordpress/wordpress.yaml b/examples/wordpress/wordpress.yaml new file mode 100644 index 000000000..b401897ab --- /dev/null +++ b/examples/wordpress/wordpress.yaml @@ -0,0 +1,6 @@ +imports: +- path: wordpress.jinja + +resources: +- name: wordpress + type: wordpress.jinja diff --git a/manager/manager/types.go b/manager/manager/types.go index 90ca61988..f733b7277 100644 --- a/manager/manager/types.go +++ b/manager/manager/types.go @@ -25,6 +25,9 @@ var Primitives = map[string]bool{ "Service": true, "Namespace": true, "Volume": true, + "Endpoints": true, + "PersistentVolumeClaim": true, + "PersistentVolume": true, } // SchemaImport represents an import as declared in a schema file. diff --git a/templates/nfs/v1/nfs.jinja b/templates/nfs/v1/nfs.jinja new file mode 100644 index 000000000..d8aca3117 --- /dev/null +++ b/templates/nfs/v1/nfs.jinja @@ -0,0 +1,50 @@ +{% set PROPERTIES = properties or {} %} +{% set SERVER_IP = PROPERTIES['ip'] or '10.0.253.247' %} +{% set SERVER_PORT = PROPERTIES['port'] or 2049 %} +{% set SERVER_DISK = PROPERTIES['disk'] or 'nfs-disk' %} +{% set SERVER_DISK_FSTYPE = PROPERTIES['fstype'] or 'ext4' %} +{% set CLAIM_NAME = PROPERTIES['claim-name'] or 'nfs' %} + +resources: +- name: nfs + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/replicatedservice/v2/replicatedservice.py + properties: + service_port: {{ SERVER_PORT }} + container_port: {{ SERVER_PORT }} + replicas: 1 # Has to be 1 because of the persistent disk + image: gcr.io/google_containers/volume-nfs + privileged: true + cluster_ip: {{ SERVER_IP }} + volumes: + - mount_path: /mnt/data + gcePersistentDisk: + pdName: {{ SERVER_DISK }} + fsType: {{ SERVER_DISK_FSTYPE }} +- name: nfs-pvc + type: PersistentVolumeClaim + properties: + kind: PersistentVolumeClaim + apiVersion: v1 + metadata: + name: {{ CLAIM_NAME }} + spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Mi +- name: nfs-pv + type: PersistentVolume + properties: + apiVersion: v1 + kind: PersistentVolume + metadata: + name: {{ CLAIM_NAME }} + spec: + capacity: + storage: 1Mi + accessModes: + - ReadWriteMany + nfs: + server: {{ SERVER_IP }} + path: "/" diff --git a/templates/nfs/v1/nfs.schema b/templates/nfs/v1/nfs.schema new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/templates/nfs/v1/nfs.schema @@ -0,0 +1 @@ + diff --git a/templates/nfs/v1/nfs.yaml b/templates/nfs/v1/nfs.yaml new file mode 100644 index 000000000..4d5c68d8a --- /dev/null +++ b/templates/nfs/v1/nfs.yaml @@ -0,0 +1,2 @@ +- name: nfs + type: https://raw.githubusercontent.com/leendersr/deployment-manager/master/templates/nfs/v1/nfs.jinja diff --git a/templates/replicatedservice/v2/replicatedservice.py b/templates/replicatedservice/v2/replicatedservice.py new file mode 100644 index 000000000..a6ef0d71e --- /dev/null +++ b/templates/replicatedservice/v2/replicatedservice.py @@ -0,0 +1,194 @@ +"""Defines a ReplicatedService type by creating both a Service and an RC. + +This module creates a typical abstraction for running a service in a +Kubernetes cluster, namely a replication controller and a service packaged +together into a single unit. +""" + +import yaml + +SERVICE_TYPE_COLLECTION = 'Service' +RC_TYPE_COLLECTION = 'ReplicationController' + + +def GenerateConfig(context): + """Generates a Replication Controller and a matching Service. + + Args: + context: Template context. See schema for context properties + + Returns: + A Container Manifest as a YAML string. + """ + # YAML config that we're going to create for both RC & Service + config = {'resources': []} + + name = context.env['name'] + container_name = context.properties.get('container_name', name) + namespace = context.properties.get('namespace', 'default') + + # Define things that the Service cares about + service_name = context.properties.get('service_name', name + '-service') + service_type = SERVICE_TYPE_COLLECTION + + # Define things that the Replication Controller (rc) cares about + rc_name = context.properties.get('rc_name', name + '-rc') + rc_type = RC_TYPE_COLLECTION + + service = { + 'name': service_name, + 'type': service_type, + 'properties': { + 'apiVersion': 'v1', + 'kind': 'Service', + 'namespace': namespace, + 'metadata': { + 'name': service_name, + 'labels': GenerateLabels(context, service_name), + }, + 'spec': { + 'ports': [GenerateServicePorts(context, container_name)], + 'selector': GenerateLabels(context, name) + } + } + } + set_up_external_lb = context.properties.get('external_service', None) + if set_up_external_lb: + service['properties']['spec']['type'] = 'LoadBalancer' + cluster_ip = context.properties.get('cluster_ip', None) + if cluster_ip: + service['properties']['spec']['clusterIP'] = cluster_ip + config['resources'].append(service) + + rc = { + 'name': rc_name, + 'type': rc_type, + 'properties': { + 'apiVersion': 'v1', + 'kind': 'ReplicationController', + 'namespace': namespace, + 'metadata': { + 'name': rc_name, + 'labels': GenerateLabels(context, rc_name), + }, + 'spec': { + 'replicas': context.properties['replicas'], + 'selector': GenerateLabels(context, name), + 'template': { + 'metadata': { + 'labels': GenerateLabels(context, name), + }, + 'spec': { + 'containers': [ + { + 'env': GenerateEnv(context), + 'name': container_name, + 'image': context.properties['image'], + 'ports': [ + { + 'name': container_name, + 'containerPort': context.properties['container_port'], + } + ], + } + ], + } + } + } + } + } + + # Set up volume mounts + if context.properties.get('volumes', None): + rc['properties']['spec']['template']['spec']['containers'][0]['volumeMounts'] = [] + rc['properties']['spec']['template']['spec']['volumes'] = [] + for volume in context.properties['volumes']: + # mountPath should be unique + volume_name = volume['mount_path'].replace('/', '-').lstrip('-') + '-storage' + rc['properties']['spec']['template']['spec']['containers'][0]['volumeMounts'].append( + { + 'name': volume_name, + 'mountPath': volume['mount_path'] + } + ) + del volume['mount_path'] + volume['name'] = volume_name + rc['properties']['spec']['template']['spec']['volumes'].append(volume) + + if context.properties.get('privileged', False): + rc['properties']['spec']['template']['spec']['containers'][0]['securityContext'] = { + 'privileged': True + } + + config['resources'].append(rc) + return yaml.dump(config) + + +# Generates labels either from the context.properties['labels'] or generates +# a default label 'name':name +def GenerateLabels(context, name): + """Generates labels from context.properties['labels'] or creates default. + + We make a deep copy of the context.properties['labels'] section to avoid + linking in the yaml document, which I believe reduces readability of the + expanded template. If no labels are given, generate a default 'name':name. + + Args: + context: Template context, which can contain the following properties: + labels - Labels to generate + + Returns: + A dict containing labels in a name:value format + """ + tmp_labels = context.properties.get('labels', None) + ret_labels = {'name': name} + if isinstance(tmp_labels, dict): + for key, value in tmp_labels.iteritems(): + ret_labels[key] = value + return ret_labels + + +def GenerateServicePorts(context, name): + """Generates a ports section for a service. + + Args: + context: Template context, which can contain the following properties: + service_port - Port to use for the service + target_port - Target port for the service + protocol - Protocol to use. + + Returns: + A dict containing a port definition + """ + service_port = context.properties.get('service_port', None) + target_port = context.properties.get('target_port', None) + protocol = context.properties.get('protocol') + + ports = {} + if name: + ports['name'] = name + if service_port: + ports['port'] = service_port + if target_port: + ports['targetPort'] = target_port + if protocol: + ports['protocol'] = protocol + + return ports + +def GenerateEnv(context): + """Generates environmental variables for a pod. + + Args: + context: Template context, which can contain the following properties: + env - Environment variables to set. + + Returns: + A list containing env variables in dict format {name: 'name', value: 'value'} + """ + env = [] + tmp_env = context.properties.get('env', []) + for entry in tmp_env: + if isinstance(entry, dict): + env.append({'name': entry.get('name'), 'value': entry.get('value')}) + return env diff --git a/templates/replicatedservice/v2/replicatedservice.py.schema b/templates/replicatedservice/v2/replicatedservice.py.schema new file mode 100644 index 000000000..712ffd315 --- /dev/null +++ b/templates/replicatedservice/v2/replicatedservice.py.schema @@ -0,0 +1,91 @@ +info: + title: Replicated Service + description: | + Defines a ReplicatedService type by creating both a Service and an RC. + + This module creates a typical abstraction for running a service in a + Kubernetes cluster, namely a replication controller and a service packaged + together into a single unit. + +required: +- image + +properties: + container_name: + type: string + description: Name to use for container. If omitted, name is used. + service_name: + type: string + description: Name to use for service. If omitted, name-service is used. + namespace: + type: string + description: Namespace to create resources in. If omitted, 'default' is + used. + default: default + protocol: + type: string + description: Protocol to use for the service. + service_port: + type: int + description: Port to use for the service. + target_port: + type: int + description: Target port to use for the service. + container_port: + type: int + description: Port to use for the container. + replicas: + type: int + description: Number of replicas to create in RC. + image: + type: string + description: Docker image to use for replicas. + labels: + type: object + description: Labels to apply. + env: + type: array + description: Environment variables to apply. + properties: + name: + type: string + value: + type: string + external_service: + type: boolean + description: If set to true, enable external load balancer. + cluster_ip: + type: string + description: IP to use for the service + privileged: + type: boolean + description: If set to true, enable privileged container + volumes: + type: array + description: Volumes to mount. + items: + type: object + properties: + mounth_path: + type: string + description: Path to mount volume + # See https://cloud.google.com/container-engine/docs/spec-schema?hl=en for possible volumes. Since we only use gcePersistentDisk and NFS in our examples we have only added these ones. + oneOf: + gcePersistentDisk: + pdName: + type: string + description: Persistent's disk name + fsType: + type: string + description: Filesystem type of the persistent disk + nfs: + server: + type: string + description: The hostname or IP address of the NFS server + path: + type: string + description: The path that is exported by the NFS server + readOnly: + type: boolean + description: Forces the NFS export to be mounted with read-only permissions +