diff --git a/.gitignore b/.gitignore index a94ac0b20d5..4384826f1e6 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,9 @@ doc_tmp/ # CoreOS stuff cluster/libvirt-coreos/coreos_*.img +# Juju Stuff +cluster/juju/charms/* + # Downloaded Kubernetes binary release kubernetes/ diff --git a/cluster/juju/bundles/local.yaml b/cluster/juju/bundles/local.yaml index 6b1e8bf2b49..907022b1e6c 100644 --- a/cluster/juju/bundles/local.yaml +++ b/cluster/juju/bundles/local.yaml @@ -1,53 +1,18 @@ -kubernetes-local: - services: - kubernetes-master: - charm: local:trusty/kubernetes-master - annotations: - "gui-x": "600" - "gui-y": "0" - expose: true - options: - version: "local" - docker: - charm: cs:trusty/docker - num_units: 2 - options: - latest: true - annotations: - "gui-x": "0" - "gui-y": "0" - flannel-docker: - charm: cs:~kubernetes/trusty/flannel-docker - annotations: - "gui-x": "0" - "gui-y": "300" - kubernetes: - charm: local:trusty/kubernetes - annotations: - "gui-x": "300" - "gui-y": "300" - etcd: - charm: cs:~kubernetes/trusty/etcd - annotations: - "gui-x": "300" - "gui-y": "0" - relations: - - - "flannel-docker:network" - - "docker:network" - - - "flannel-docker:network" - - "kubernetes-master:network" - - - "flannel-docker:docker-host" - - "docker:juju-info" - - - "flannel-docker:docker-host" - - "kubernetes-master:juju-info" - - - "flannel-docker:db" - - "etcd:client" - - - "kubernetes:docker-host" - - "docker:juju-info" - - - "etcd:client" - - "kubernetes:etcd" - - - "etcd:client" - - "kubernetes-master:etcd" - - - "kubernetes-master:minions-api" - - "kubernetes:api" - series: trusty +services: + kubernetes: + charm: local:trusty/kubernetes + annotations: + "gui-x": "600" + "gui-y": "0" + expose: true + num_units: 2 + etcd: + charm: cs:~containers/trusty/etcd + annotations: + "gui-x": "300" + "gui-y": "0" + num_units: 1 +relations: + - - "kubernetes:etcd" + - "etcd:db" +series: trusty diff --git a/cluster/juju/charms/trusty/.gitignore b/cluster/juju/charms/trusty/.gitignore deleted file mode 100644 index b3d791e0788..00000000000 --- a/cluster/juju/charms/trusty/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/docker \ No newline at end of file diff --git a/cluster/juju/charms/trusty/kubernetes-master/.bzrignore b/cluster/juju/charms/trusty/kubernetes-master/.bzrignore deleted file mode 100644 index 6b8710a711f..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/.bzrignore +++ /dev/null @@ -1 +0,0 @@ -.git diff --git a/cluster/juju/charms/trusty/kubernetes-master/.gitignore b/cluster/juju/charms/trusty/kubernetes-master/.gitignore deleted file mode 100644 index 48a383a0c99..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -*~ -.bzr -.venv -unit_tests/__pycache__ -*.pyc diff --git a/cluster/juju/charms/trusty/kubernetes-master/.vendor-rc b/cluster/juju/charms/trusty/kubernetes-master/.vendor-rc deleted file mode 100644 index 87619d5117c..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/.vendor-rc +++ /dev/null @@ -1,5 +0,0 @@ -omit: -- .git -- .gitignore -- .gitmodules -- revision diff --git a/cluster/juju/charms/trusty/kubernetes-master/Makefile b/cluster/juju/charms/trusty/kubernetes-master/Makefile deleted file mode 100644 index 50c46d395c8..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/Makefile +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2016 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -build: virtualenv lint test - -virtualenv: - virtualenv .venv - .venv/bin/pip install -q -r requirements.txt - -lint: virtualenv - @.venv/bin/flake8 hooks --exclude=charmhelpers --ignore=W391 - @.venv/bin/charm proof - -test: virtualenv - @CHARM_DIR=. PYTHONPATH=./hooks .venv/bin/py.test -v unit_tests/* - -functional-test: - @bundletester - -release: check-path virtualenv - @.venv/bin/pip install git-vendor - @.venv/bin/git-vendor sync -d ${KUBERNETES_MASTER_BZR} - -check-path: -ifndef KUBERNETES_MASTER_BZR - $(error KUBERNETES_MASTER_BZR is undefined) -endif - -clean: - rm -rf .venv - find -name *.pyc -delete - rm -rf unit_tests/.cache diff --git a/cluster/juju/charms/trusty/kubernetes-master/README.md b/cluster/juju/charms/trusty/kubernetes-master/README.md deleted file mode 100644 index ff709d3e27f..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/README.md +++ /dev/null @@ -1,126 +0,0 @@ -# Kubernetes Master Charm - -[Kubernetes](https://github.com/kubernetes/kubernetes) is an open -source system for managing containerized applications across multiple hosts. -Kubernetes uses [Docker](http://www.docker.io/) to package, instantiate and run -containerized applications. - -The Kubernetes Juju charms enable you to run Kubernetes on all the cloud -platforms that Juju supports. - -A Kubernetes deployment consists of several independent charms that can be -scaled to meet your needs - -### Etcd -Etcd is a key value store for Kubernetes. All persistent master state -is stored in `etcd`. - -### Flannel-docker -Flannel is a -[software defined networking](http://en.wikipedia.org/wiki/Software-defined_networking) -component that provides individual subnets for each machine in the cluster. - -### Docker -Docker is an open platform for distributing applications for system administrators. - -### Kubernetes master -The controlling unit in a Kubernetes cluster is called the master. It is the -main management contact point providing many management services for the worker -nodes. - -### Kubernetes minion -The servers that perform the work are known as minions. Minions must be able to -communicate with the master and run the workloads that are assigned to them. - - -## Usage - - -#### Deploying the Development Focus - -To deploy a Kubernetes environment in Juju : - - juju deploy cs:~kubernetes/trusty/etcd - juju deploy cs:trusty/flannel-docker - juju deploy cs:trusty/docker - juju deploy local:trusty/kubernetes-master - juju deploy local:trusty/kubernetes - - juju add-relation etcd flannel-docker - juju add-relation flannel-docker:network docker:network - juju add-relation flannel-docker:docker-host docker - juju add-relation etcd kubernetes - juju add-relation etcd kubernetes-master - juju add-relation kubernetes kubernetes-master - - -#### Deploying the recommended configuration - -Use the 'juju quickstart' command to deploy a Kubernetes cluster to any cloud -supported by Juju. - -The charm store version of the Kubernetes bundle can be deployed as follows: - - juju quickstart u/kubernetes/kubernetes-cluster - -> Note: The charm store bundle may be locked to a specific Kubernetes release. - -Alternately you could deploy a Kubernetes bundle straight from github or a file: - - juju quickstart https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/juju/bundles/local.yaml - -The command above does few things for you: - -- Starts a curses based gui for managing your cloud or MAAS credentials -- Looks for a bootstrapped deployment environment, and bootstraps if - required. This will launch a bootstrap node in your chosen - deployment environment (machine 0). -- Deploys the Juju GUI to your environment onto the bootstrap node. -- Provisions 4 machines, and deploys the Kubernetes services on top of - them (Kubernetes-master, two Kubernetes minions using flannel, and etcd). -- Orchestrates the relations among the services, and exits. - -Now you should have a running Kubernetes. Run `juju status ---format=oneline` to see the address of your kubernetes-master unit. - -For further reading on [Juju Quickstart](https://pypi.python.org/pypi/juju-quickstart) - -Go to the [Getting started with Juju guide](https://github.com/kubernetes/kubernetes/blob/master/docs/getting-started-guides/juju.md) -for more information about deploying a development Kubernetes cluster. - - -#### Post Deployment - -To interact with the kubernetes environment, either build or -[download](https://github.com/kubernetes/kubernetes/releases) the -[kubectl](https://github.com/kubernetes/kubernetes/blob/master/docs/user-guide/kubectl/kubectl.md) -binary (available in the releases binary tarball) and point it to the master with : - - - $ juju status kubernetes-master | grep public - public-address: 104.131.108.99 - $ export KUBERNETES_MASTER="104.131.108.99" - -# Configuration -For you convenience this charm supports changing the version of kubernetes binaries. -This can be done through the Juju GUI or on the command line: - - juju set kubernetes version=”v0.10.0” - -If the charm does not already contain the tar file with the desired architecture -and version it will attempt to download the kubernetes binaries using the gsutil -command. - -Congratulations you know have deployed a Kubernetes environment! Use the -[kubectl](https://github.com/kubernetes/kubernetes/blob/master/docs/user-guide/kubectl/kubectl.md) -to interact with the environment. - -# Kubernetes information - -- [Kubernetes github project](https://github.com/kubernetes/kubernetes) -- [Kubernetes issue tracker](https://github.com/kubernetes/kubernetes/issues) -- [Kubernetes Documenation](https://github.com/kubernetes/kubernetes/tree/master/docs) -- [Kubernetes releases](https://github.com/kubernetes/kubernetes/releases) - - -[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/cluster/juju/charms/trusty/kubernetes-master/README.md?pixel)]() diff --git a/cluster/juju/charms/trusty/kubernetes-master/config.yaml b/cluster/juju/charms/trusty/kubernetes-master/config.yaml deleted file mode 100644 index d34463ce368..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/config.yaml +++ /dev/null @@ -1,33 +0,0 @@ -options: - version: - type: string - default: "v1.0.0" - description: | - The kubernetes release to use in this charm. The binary files are - compiled from the source identified by this tag in github. Using the - value of "source" will use the master kubernetes branch when compiling - the binaries. - username: - type: string - default: "admin" - description: | - The initial user for the kubernetes basic authentication file. - password: - type: string - default: "" - description: | - The password for the kubernetes basic authentication. If this value is - empty, a password will be generated at random for the username. - apiserver-cert: - type: string - default: "" - description: | - The ssl certificate to use for tls communication to the Kubernetes api - server. If this value is empty a self signed certificate and key will - be generated. - apiserver-key: - type: string - default: "" - description: | - The private key to use for tls communication to the Kubernetes api - server. If this value is empty a key and certificate will be generated. diff --git a/cluster/juju/charms/trusty/kubernetes-master/copyright b/cluster/juju/charms/trusty/kubernetes-master/copyright deleted file mode 100644 index a0b409a8e84..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/copyright +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/cluster/juju/charms/trusty/kubernetes-master/files/apiserver.upstart.tmpl b/cluster/juju/charms/trusty/kubernetes-master/files/apiserver.upstart.tmpl deleted file mode 100644 index a171f29c27f..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/files/apiserver.upstart.tmpl +++ /dev/null @@ -1,20 +0,0 @@ -description "Kubernetes Controller" - -start on runlevel [2345] -stop on runlevel [!2345] - -limit nofile 20000 20000 - -kill timeout 30 # wait 30s between SIGTERM and SIGKILL. - -exec /usr/local/bin/apiserver \ - --allow-privileged=true \ - --basic-auth-file=/srv/kubernetes/basic-auth.csv \ - --bind-address=%(api_private_address)s \ - --etcd-servers=%(etcd_servers)s \ - --insecure-bind-address=%(api_private_address)s \ - --logtostderr=true \ - --secure-port=6443 \ - --service-cluster-ip-range=10.244.240.0/20 \ - --tls-cert-file=/srv/kubernetes/apiserver.crt \ - --tls-private-key-file=/srv/kubernetes/apiserver.key diff --git a/cluster/juju/charms/trusty/kubernetes-master/files/controller-manager.upstart.tmpl b/cluster/juju/charms/trusty/kubernetes-master/files/controller-manager.upstart.tmpl deleted file mode 100644 index 8279e391133..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/files/controller-manager.upstart.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -description "Kubernetes Controller" - -start on runlevel [2345] -stop on runlevel [!2345] - -limit nofile 20000 20000 - -kill timeout 30 # wait 30s between SIGTERM and SIGKILL. - -exec /usr/local/bin/controller-manager \ - --address=%(bind_address)s \ - --logtostderr=true \ - --master=%(api_http_uri)s diff --git a/cluster/juju/charms/trusty/kubernetes-master/files/distribution.conf.tmpl b/cluster/juju/charms/trusty/kubernetes-master/files/distribution.conf.tmpl deleted file mode 100644 index 6ae91f14468..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/files/distribution.conf.tmpl +++ /dev/null @@ -1,10 +0,0 @@ -# This file configures ngnix to serve Kubernetes binaries using http. -# The charms find the location path from the api relation to the charm. -server { - listen 80 default_server; - root %(alias)s; - - location %(web_uri)s { - alias %(alias)s; - } -} diff --git a/cluster/juju/charms/trusty/kubernetes-master/files/nginx.conf.tmpl b/cluster/juju/charms/trusty/kubernetes-master/files/nginx.conf.tmpl deleted file mode 100644 index 9c9f31d2891..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/files/nginx.conf.tmpl +++ /dev/null @@ -1,27 +0,0 @@ -# Proxy HTTPS from the public address to the kube-apiserver running at 6443. -server { - listen 443; - server_name localhost; - - root html; - index index.html index.htm; - - ssl on; - ssl_certificate /srv/kubernetes/apiserver.crt; - ssl_certificate_key /srv/kubernetes/apiserver.key; - ssl_session_timeout 5m; - - # don't use SSLv3 because of POODLE - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS; - ssl_prefer_server_ciphers on; - - location / { - proxy_buffering off; - proxy_pass %(api_https_uri)s; - proxy_connect_timeout 159s; - proxy_send_timeout 600s; - proxy_read_timeout 600s; - proxy_redirect off; - } -} diff --git a/cluster/juju/charms/trusty/kubernetes-master/files/scheduler.upstart.tmpl b/cluster/juju/charms/trusty/kubernetes-master/files/scheduler.upstart.tmpl deleted file mode 100644 index 4500dccac4c..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/files/scheduler.upstart.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -description "Kubernetes Scheduler" - -start on runlevel [2345] -stop on runlevel [!2345] - -limit nofile 20000 20000 - -kill timeout 30 # wait 30s between SIGTERM and SIGKILL. - -exec /usr/local/bin/scheduler \ - --address=%(bind_address)s \ - --logtostderr=true \ - --master=%(api_http_uri)s diff --git a/cluster/juju/charms/trusty/kubernetes-master/hooks/__init__.py b/cluster/juju/charms/trusty/kubernetes-master/hooks/__init__.py deleted file mode 100644 index 8eefd60722b..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/hooks/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/cluster/juju/charms/trusty/kubernetes-master/hooks/config-changed b/cluster/juju/charms/trusty/kubernetes-master/hooks/config-changed deleted file mode 120000 index 9416ca6ac28..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/hooks/config-changed +++ /dev/null @@ -1 +0,0 @@ -hooks.py \ No newline at end of file diff --git a/cluster/juju/charms/trusty/kubernetes-master/hooks/etcd-relation-changed b/cluster/juju/charms/trusty/kubernetes-master/hooks/etcd-relation-changed deleted file mode 120000 index 9416ca6ac28..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/hooks/etcd-relation-changed +++ /dev/null @@ -1 +0,0 @@ -hooks.py \ No newline at end of file diff --git a/cluster/juju/charms/trusty/kubernetes-master/hooks/hooks.py b/cluster/juju/charms/trusty/kubernetes-master/hooks/hooks.py deleted file mode 100755 index 55393544eda..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/hooks/hooks.py +++ /dev/null @@ -1,326 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -The main hook file is called by Juju. -""" -import contextlib -import os -import socket -import subprocess -import sys -from charmhelpers.core import hookenv, host -from charmhelpers.contrib import ssl -from kubernetes_installer import KubernetesInstaller -from path import Path - -hooks = hookenv.Hooks() - - -@contextlib.contextmanager -def check_sentinel(filepath): - """ - A context manager method to write a file while the code block is doing - something and remove the file when done. - """ - fail = False - try: - yield filepath.exists() - except: - fail = True - filepath.touch() - raise - finally: - if fail is False and filepath.exists(): - filepath.remove() - - -@hooks.hook('config-changed') -def config_changed(): - """ - On the execution of the juju event 'config-changed' this function - determines the appropriate architecture and the configured version to - create kubernetes binary files. - """ - hookenv.log('Starting config-changed') - charm_dir = Path(hookenv.charm_dir()) - config = hookenv.config() - # Get the version of kubernetes to install. - version = config['version'] - username = config['username'] - password = config['password'] - certificate = config['apiserver-cert'] - key = config['apiserver-key'] - - if version == 'master': - # The 'master' branch of kuberentes is used when master is configured. - branch = 'master' - elif version == 'local': - # Check for kubernetes binaries in the local files/output directory. - branch = None - else: - # Create a branch to a tag to get the release version. - branch = 'tags/{0}'.format(version) - - cert_file = '/srv/kubernetes/apiserver.crt' - key_file = '/srv/kubernetes/apiserver.key' - # When the cert or key changes we need to restart the apiserver. - if config.changed('apiserver-cert') or config.changed('apiserver-key'): - hookenv.log('Certificate or key has changed.') - if not certificate or not key: - generate_cert(key=key_file, cert=cert_file) - else: - hookenv.log('Writing new certificate and key to server.') - with open(key_file, 'w') as file: - file.write(key) - with open(cert_file, 'w') as file: - file.write(certificate) - # Restart apiserver as the certificate or key has changed. - if host.service_running('apiserver'): - host.service_restart('apiserver') - # Reload nginx because it proxies https to apiserver. - if host.service_running('nginx'): - host.service_reload('nginx') - - if config.changed('username') or config.changed('password'): - hookenv.log('Username or password changed, creating authentication.') - basic_auth(username, username, password) - if host.service_running('apiserver'): - host.service_restart('apiserver') - - # Get package architecture, rather than arch from the kernel (uname -m). - arch = subprocess.check_output(['dpkg', '--print-architecture']).strip() - - if not branch: - output_path = charm_dir / 'files/output' - kube_installer = KubernetesInstaller(arch, version, output_path) - else: - - # Build the kuberentes binaries from source on the units. - kubernetes_dir = Path('/opt/kubernetes') - - # Construct the path to the binaries using the arch. - output_path = kubernetes_dir / '_output/local/bin/linux' / arch - kube_installer = KubernetesInstaller(arch, version, output_path) - - if not kubernetes_dir.exists(): - message = 'The kubernetes source directory {0} does not exist. ' \ - 'Was the kubernetes repository cloned during the install?' - print(message.format(kubernetes_dir)) - exit(1) - - # Change to the kubernetes directory (git repository). - with kubernetes_dir: - # Create a command to get the current branch. - git_branch = 'git branch | grep "\*" | cut -d" " -f2' - current_branch = subprocess.check_output(git_branch, shell=True) - current_branch = current_branch.strip() - print('Current branch: ', current_branch) - # Create the path to a file to indicate if the build was broken. - broken_build = charm_dir / '.broken_build' - # write out the .broken_build file while this block is executing. - with check_sentinel(broken_build) as last_build_failed: - print('Last build failed: ', last_build_failed) - # Rebuild if current version is different or last build failed. - if current_branch != version or last_build_failed: - kube_installer.build(branch) - if not output_path.isdir(): - broken_build.touch() - - # Create the symoblic links to the right directories. - kube_installer.install() - - relation_changed() - - hookenv.log('The config-changed hook completed successfully.') - - -@hooks.hook('etcd-relation-changed', 'minions-api-relation-changed') -def relation_changed(): - template_data = get_template_data() - - # Check required keys - for k in ('etcd_servers',): - if not template_data.get(k): - print 'Missing data for', k, template_data - return - - print 'Running with\n', template_data - - # Render and restart as needed - for n in ('apiserver', 'controller-manager', 'scheduler'): - if render_file(n, template_data) or not host.service_running(n): - host.service_restart(n) - - # Render the file that makes the kubernetes binaries available to minions. - if render_file( - 'distribution', template_data, - 'conf.tmpl', '/etc/nginx/sites-enabled/distribution') or \ - not host.service_running('nginx'): - host.service_reload('nginx') - # Render the default nginx template. - if render_file( - 'nginx', template_data, - 'conf.tmpl', '/etc/nginx/sites-enabled/default') or \ - not host.service_running('nginx'): - host.service_reload('nginx') - - # Send api endpoint to minions - notify_minions() - - -@hooks.hook('network-relation-changed') -def network_relation_changed(): - relation_id = hookenv.relation_id() - hookenv.relation_set(relation_id, ignore_errors=True) - - -def notify_minions(): - print('Notify minions.') - config = hookenv.config() - for r in hookenv.relation_ids('minions-api'): - hookenv.relation_set( - r, - hostname=hookenv.unit_private_ip(), - port=8080, - version=config['version']) - print('Notified minions of version ' + config['version']) - - -def basic_auth(name, id, pwd=None, file='/srv/kubernetes/basic-auth.csv'): - """ - Create a basic authentication file for kubernetes. The file is a csv file - with 3 columns: password, user name, user id. From the Kubernetes docs: - The basic auth credentials last indefinitely, and the password cannot be - changed without restarting apiserver. - """ - if not pwd: - import random - import string - alphanumeric = string.ascii_letters + string.digits - pwd = ''.join(random.choice(alphanumeric) for _ in range(16)) - lines = [] - auth_file = Path(file) - if auth_file.isfile(): - lines = auth_file.lines() - for line in lines: - target = ',{0},{1}'.format(name, id) - if target in line: - lines.remove(line) - auth_line = '{0},{1},{2}'.format(pwd, name, id) - lines.append(auth_line) - auth_file.write_lines(lines) - - -def generate_cert(common_name=None, - key='/srv/kubernetes/apiserver.key', - cert='/srv/kubernetes/apiserver.crt'): - """ - Create the certificate and key for the Kubernetes tls enablement. - """ - hookenv.log('Generating new self signed certificate and key', 'INFO') - if not common_name: - common_name = hookenv.unit_get('public-address') - if os.path.isfile(key) or os.path.isfile(cert): - hookenv.log('Overwriting the existing certificate or key', 'WARNING') - hookenv.log('Generating certificate for {0}'.format(common_name), 'INFO') - # Generate the self signed certificate with the public address as CN. - # https://pythonhosted.org/charmhelpers/api/charmhelpers.contrib.ssl.html - ssl.generate_selfsigned(key, cert, cn=common_name) - - -def get_template_data(): - rels = hookenv.relations() - config = hookenv.config() - version = config['version'] - template_data = {} - template_data['etcd_servers'] = ','.join([ - 'http://%s:%s' % (s[0], s[1]) for s in sorted( - get_rel_hosts('etcd', rels, ('hostname', 'port')))]) - template_data['minions'] = ','.join(get_rel_hosts('minions-api', rels)) - private_ip = hookenv.unit_private_ip() - public_ip = hookenv.unit_public_ip() - template_data['api_public_address'] = _bind_addr(public_ip) - template_data['api_private_address'] = _bind_addr(private_ip) - template_data['bind_address'] = '127.0.0.1' - template_data['api_http_uri'] = 'http://%s:%s' % (private_ip, 8080) - template_data['api_https_uri'] = 'https://%s:%s' % (private_ip, 6443) - - arch = subprocess.check_output(['dpkg', '--print-architecture']).strip() - - template_data['web_uri'] = '/kubernetes/%s/local/bin/linux/%s/' % (version, - arch) - if version == 'local': - template_data['alias'] = hookenv.charm_dir() + '/files/output/' - else: - directory = '/opt/kubernetes/_output/local/bin/linux/%s/' % arch - template_data['alias'] = directory - _encode(template_data) - return template_data - - -def _bind_addr(addr): - if addr.replace('.', '').isdigit(): - return addr - try: - return socket.gethostbyname(addr) - except socket.error: - raise ValueError('Could not resolve address %s' % addr) - - -def _encode(d): - for k, v in d.items(): - if isinstance(v, unicode): - d[k] = v.encode('utf8') - - -def get_rel_hosts(rel_name, rels, keys=('private-address',)): - hosts = [] - for r, data in rels.get(rel_name, {}).items(): - for unit_id, unit_data in data.items(): - if unit_id == hookenv.local_unit(): - continue - values = [unit_data.get(k) for k in keys] - if not all(values): - continue - hosts.append(len(values) == 1 and values[0] or values) - return hosts - - -def render_file(name, data, src_suffix='upstart.tmpl', tgt_path=None): - tmpl_path = os.path.join( - os.environ.get('CHARM_DIR'), 'files', '%s.%s' % (name, src_suffix)) - - with open(tmpl_path) as fh: - tmpl = fh.read() - rendered = tmpl % data - - if tgt_path is None: - tgt_path = '/etc/init/%s.conf' % name - - if os.path.exists(tgt_path): - with open(tgt_path) as fh: - contents = fh.read() - if contents == rendered: - return False - - with open(tgt_path, 'w') as fh: - fh.write(rendered) - return True - - -if __name__ == '__main__': - hooks.execute(sys.argv) diff --git a/cluster/juju/charms/trusty/kubernetes-master/hooks/install b/cluster/juju/charms/trusty/kubernetes-master/hooks/install deleted file mode 120000 index 7f4fe4b083e..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/hooks/install +++ /dev/null @@ -1 +0,0 @@ -install.py \ No newline at end of file diff --git a/cluster/juju/charms/trusty/kubernetes-master/hooks/install.py b/cluster/juju/charms/trusty/kubernetes-master/hooks/install.py deleted file mode 100755 index 70193117f62..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/hooks/install.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import setup -setup.pre_install() -import subprocess - -from charmhelpers import fetch -from charmhelpers.core import hookenv -from charmhelpers.fetch import archiveurl -from path import Path - - -def install(): - install_packages() - hookenv.log('Installing go') - download_go() - - hookenv.log('Adding kubernetes and go to the path') - address = hookenv.unit_private_ip() - strings = [ - 'export GOROOT=/usr/local/go\n', - 'export PATH=$PATH:$GOROOT/bin\n', - 'export KUBERNETES_MASTER=http://{0}:8080\n'.format(address), - ] - update_rc_files(strings) - hookenv.log('Downloading kubernetes code') - clone_repository() - - # Create the directory to store the keys and auth files. - srv = Path('/srv/kubernetes') - if not srv.isdir(): - srv.makedirs_p() - - hookenv.open_port(8080) - hookenv.open_port(6443) - hookenv.open_port(443) - - hookenv.log('Install complete') - - -def download_go(): - """ - Kubernetes charm strives to support upstream. Part of this is installing a - fairly recent edition of GO. This fetches the golang archive and installs - it in /usr/local - """ - go_url = 'https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz' - go_sha1 = '5020af94b52b65cc9b6f11d50a67e4bae07b0aff' - handler = archiveurl.ArchiveUrlFetchHandler() - handler.install(go_url, '/usr/local', go_sha1, 'sha1') - - -def clone_repository(): - """ - Clone the upstream repository into /opt/kubernetes for deployment - compilation of kubernetes. Subsequently used during upgrades. - """ - - repository = 'https://github.com/kubernetes/kubernetes.git' - kubernetes_directory = Path('/opt/kubernetes') - # Since we can not clone twice, check for the directory and remove it. - if kubernetes_directory.isdir(): - kubernetes_directory.rmtree_p() - - command = ['git', 'clone', repository, kubernetes_directory] - print(command) - output = subprocess.check_output(command) - print(output) - - -def install_packages(): - """ - Install required packages to build the k8s source, and syndicate between - minion nodes. In addition, fetch pip to handle python dependencies - """ - hookenv.log('Installing Debian packages') - # Create the list of packages to install. - apt_packages = ['apache2-utils', - 'build-essential', - 'docker.io', - 'git', - 'make', - 'nginx', - 'python-pip', ] - fetch.apt_install(fetch.filter_installed_packages(apt_packages)) - - -def update_rc_files(strings, rc_files=None): - """ - Preseed the bash environment for ubuntu and root with K8's env vars to - make interfacing with the api easier. (see: kubectrl docs) - """ - if not rc_files: - rc_files = [Path('/home/ubuntu/.bashrc'), Path('/root/.bashrc')] - - for rc_file in rc_files: - lines = rc_file.lines() - for string in strings: - if string not in lines: - lines.append(string) - rc_file.write_lines(lines) - - -if __name__ == "__main__": - install() diff --git a/cluster/juju/charms/trusty/kubernetes-master/hooks/kubernetes_installer.py b/cluster/juju/charms/trusty/kubernetes-master/hooks/kubernetes_installer.py deleted file mode 100644 index 6c7cbc9b42b..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/hooks/kubernetes_installer.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import shlex -import subprocess -from path import Path - - -def run(command, shell=False): - """ A convience method for executing all the commands. """ - print(command) - if shell is False: - command = shlex.split(command) - output = subprocess.check_output(command, shell=shell) - print(output) - return output - - -class KubernetesInstaller(): - """ - This class contains the logic needed to install kuberentes binary files. - """ - - def __init__(self, arch, version, output_dir): - """ Gather the required variables for the install. """ - # The kubernetes-master charm needs certain commands to be aliased. - self.aliases = {'kube-apiserver': 'apiserver', - 'kube-controller-manager': 'controller-manager', - 'kube-proxy': 'kube-proxy', - 'kube-scheduler': 'scheduler', - 'kubectl': 'kubectl', - 'kubelet': 'kubelet'} - self.arch = arch - self.version = version - self.output_dir = Path(output_dir) - - def build(self, branch): - """ Build kubernetes from a github repository using the Makefile. """ - # Remove any old build artifacts. - make_clean = 'make clean' - run(make_clean) - # Always checkout the master to get the latest repository information. - git_checkout_cmd = 'git checkout master' - run(git_checkout_cmd) - # When checking out a tag, delete the old branch (not master). - if branch != 'master': - git_drop_branch = 'git branch -D {0}'.format(self.version) - print(git_drop_branch) - rc = subprocess.call(git_drop_branch.split()) - if rc != 0: - print('returned: %d' % rc) - # Make sure the git repository is up-to-date. - git_fetch = 'git fetch origin {0}'.format(branch) - run(git_fetch) - - if branch == 'master': - git_reset = 'git reset --hard origin/master' - run(git_reset) - else: - # Checkout a branch of kubernetes so the repo is correct. - checkout = 'git checkout -b {0} {1}'.format(self.version, branch) - run(checkout) - - # Create an environment with the path to the GO binaries included. - go_path = ('/usr/local/go/bin', os.environ.get('PATH', '')) - go_env = os.environ.copy() - go_env['PATH'] = ':'.join(go_path) - print(go_env['PATH']) - - # Compile the binaries with the make command using the WHAT variable. - make_what = "make all WHAT='cmd/kube-apiserver cmd/kubectl "\ - "cmd/kube-controller-manager plugin/cmd/kube-scheduler "\ - "cmd/kubelet cmd/kube-proxy'" - print(make_what) - rc = subprocess.call(shlex.split(make_what), env=go_env) - - def install(self, install_dir=Path('/usr/local/bin')): - """ Install kubernetes binary files from the output directory. """ - - if not install_dir.isdir(): - install_dir.makedirs_p() - - # Create the symbolic links to the real kubernetes binaries. - for key, value in self.aliases.iteritems(): - target = self.output_dir / key - if target.exists(): - link = install_dir / value - if link.exists(): - link.remove() - target.symlink(link) - else: - print('Error target file {0} does not exist.'.format(target)) - exit(1) diff --git a/cluster/juju/charms/trusty/kubernetes-master/hooks/minions-api-relation-changed b/cluster/juju/charms/trusty/kubernetes-master/hooks/minions-api-relation-changed deleted file mode 120000 index 9416ca6ac28..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/hooks/minions-api-relation-changed +++ /dev/null @@ -1 +0,0 @@ -hooks.py \ No newline at end of file diff --git a/cluster/juju/charms/trusty/kubernetes-master/hooks/network-relation-changed b/cluster/juju/charms/trusty/kubernetes-master/hooks/network-relation-changed deleted file mode 120000 index 9416ca6ac28..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/hooks/network-relation-changed +++ /dev/null @@ -1 +0,0 @@ -hooks.py \ No newline at end of file diff --git a/cluster/juju/charms/trusty/kubernetes-master/hooks/setup.py b/cluster/juju/charms/trusty/kubernetes-master/hooks/setup.py deleted file mode 100644 index 6afdd49bf5f..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/hooks/setup.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -def pre_install(): - """ - Do any setup required before the install hook. - """ - install_charmhelpers() - install_path() - - -def install_charmhelpers(): - """ - Install the charmhelpers library, if not present. - """ - try: - import charmhelpers # noqa - except ImportError: - import subprocess - subprocess.check_call(['apt-get', 'install', '-y', 'python-pip']) - subprocess.check_call(['pip', 'install', 'charmhelpers']) - - -def install_path(): - """ - Install the path.py library, when not present. - """ - try: - import path # noqa - except ImportError: - import subprocess - subprocess.check_call(['apt-get', 'install', '-y', 'python-pip']) - subprocess.check_call(['pip', 'install', 'path.py']) diff --git a/cluster/juju/charms/trusty/kubernetes-master/metadata.yaml b/cluster/juju/charms/trusty/kubernetes-master/metadata.yaml deleted file mode 100644 index 3d705962f9b..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/metadata.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: kubernetes-master -summary: Container Cluster Management Master -description: | - Provides a kubernetes api endpoint, scheduler for managing containers. -maintainers: - - Matt Bruzek - - Whit Morriss - - Charles Butler -tags: - - ops - - network -provides: - client-api: - interface: kubernetes-client - minions-api: - interface: kubernetes-api -requires: - etcd: - interface: etcd - network: - interface: overlay-network diff --git a/cluster/juju/charms/trusty/kubernetes-master/notes.txt b/cluster/juju/charms/trusty/kubernetes-master/notes.txt deleted file mode 100644 index f266cabef81..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/notes.txt +++ /dev/null @@ -1,75 +0,0 @@ -kubernetes-master ------------------ - -notes on src ------------- - current provider responsibilities - - instances - - load blanacers - - zones (not useful as its only for apiserver). - - provider functionality currently hardcoded to gce across codebase - - persistent storage - - -ideas ------ - - juju provider impl - - file provider for machines/minions - - openvpn as overlay per extant salt config. - -cloud ------ - -todo ----- - - token auth file - - format csv -> token, user, uid - - config privileged - - config log-level - - config / check logs collection endpoint - - config / version and binary location via url - -Q/A ----- - -https://botbot.me/freenode/google-containers/2014-10-17/?msg=23696683&page=6 - -Q. The new volumes/storage provider api appears to be hardcoded to -gce.. Is there a plan to abstract that anytime soon? -A. effectively it is abstract enough for the moment, no plans to -change, but willing subject to suitable abstraction. - -Q.The zone provider api appears to return the address only of the api -server afaics. How is that useful? afaics the better semantic would be -an attribute on the minions to instantiate multiple templates across -zones? -A. apparently not considered, current solution for ha is multiple k8s -per zone with external lb. pointed out this was inane. - - -Q. Several previous platforms supported have been moved to the icebox, -just curious what was subject to bitrot. the salt/shell script for -those platforms or something more api intrinsic? -A. apparently the change to ship binaries instead of build from src -broke them.. somehow. - -Q. i'm mostly interested in flannel due to its portability. Does the -inter pod networking setup need to include the other components of the -system, ie does api talk directly to containers, or only via kubelet. -A. api server only talks to kubelet - - -Q. Status of HA? -A. not done yet, election package merged, nothing using it. - -Afaics design discussion doesn't take place on the list. - -Q. Is minion registration supported, ie. bypassing cloud provider -filter all instances via regex match? -A. not done yet, pull request in review for minions in etcd (not -found, perhaps merged) - -------------- -cadvisor usage helper -https://github.com/GoogleCloudPlatform/heapster diff --git a/cluster/juju/charms/trusty/kubernetes-master/requirements.txt b/cluster/juju/charms/trusty/kubernetes-master/requirements.txt deleted file mode 100644 index 0cd4a6a2d65..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -flake8 -pytest -bundletester -path.py -charmhelpers diff --git a/cluster/juju/charms/trusty/kubernetes-master/unit_tests/kubernetes_installer_test.py b/cluster/juju/charms/trusty/kubernetes-master/unit_tests/kubernetes_installer_test.py deleted file mode 100644 index 4c7c7946233..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/unit_tests/kubernetes_installer_test.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from mock import patch -from mock import ANY -from path import Path -import pytest -import subprocess -import sys - -# Add the hooks directory to the python path. -hooks_dir = Path('__file__').parent.abspath() / 'hooks' -sys.path.insert(0, hooks_dir.abspath()) -# Import the module to be tested. -import kubernetes_installer - - -def test_run(): - """ Test the run method both with valid commands and invalid commands. """ - ls = 'ls -l {0}/kubernetes_installer.py'.format(hooks_dir) - output = kubernetes_installer.run(ls, False) - assert output - assert 'kubernetes_installer.py' in output - output = kubernetes_installer.run(ls, True) - assert output - assert 'kubernetes_installer.py' in output - - invalid_directory = Path('/not/a/real/directory') - assert not invalid_directory.exists() - invalid_command = 'ls {0}'.format(invalid_directory) - with pytest.raises(subprocess.CalledProcessError) as error: - kubernetes_installer.run(invalid_command) - print(error) - with pytest.raises(subprocess.CalledProcessError) as error: - kubernetes_installer.run(invalid_command, shell=True) - print(error) - - -class TestKubernetesInstaller(): - - def makeone(self, *args, **kw): - """ Create the KubernetesInstaller object and return it. """ - from kubernetes_installer import KubernetesInstaller - return KubernetesInstaller(*args, **kw) - - def test_init(self): - """ Test that the init method correctly assigns the variables. """ - ki = self.makeone('i386', '3.0.1', '/tmp/does_not_exist') - assert ki.aliases - assert 'kube-apiserver' in ki.aliases - assert 'kube-controller-manager' in ki.aliases - assert 'kube-scheduler' in ki.aliases - assert 'kubectl' in ki.aliases - assert 'kubelet' in ki.aliases - assert ki.arch == 'i386' - assert ki.version == '3.0.1' - assert ki.output_dir == Path('/tmp/does_not_exist') - - @patch('kubernetes_installer.run') - @patch('kubernetes_installer.subprocess.call') - def test_build(self, cmock, rmock): - """ Test the build method with master and non-master branches. """ - directory = Path('/tmp/kubernetes_installer_test/build') - ki = self.makeone('amd64', 'v99.00.11', directory) - assert not directory.exists(), 'The %s directory exists!' % directory - # Call the build method with "master" branch. - ki.build("master") - # TODO: run is called many times but mock only remembers last one. - rmock.assert_called_with('git reset --hard origin/master') - - # TODO: call is complex and hard to verify with mock, fix that. - # this is not doing what we think it should be doing, magic mock - # makes this tricky. - # list['foo', 'baz'], env = ANY - make_args = ['make', 'all', 'WHAT=cmd/kube-apiserver cmd/kubectl cmd/kube-controller-manager plugin/cmd/kube-scheduler cmd/kubelet cmd/kube-proxy'] # noqa - cmock.assert_called_once_with(make_args, env=ANY) - - @patch('kubernetes_installer.run') - @patch('kubernetes_installer.subprocess.call') - def test_schenanigans(self, cmock, rmock): - """ Test the build method with master and non-master branches. """ - directory = Path('/tmp/kubernetes_installer_test/build') - ki = self.makeone('amd64', 'v99.00.11', directory) - assert not directory.exists(), 'The %s directory exists!' % directory - - # Call the build method with something other than "master" branch. - ki.build("branch") - # TODO: run is called many times, but mock only remembers last one. - rmock.assert_called_with('git checkout -b v99.00.11 branch') - # TODO: call is complex and hard to verify with mock, fix that. - assert cmock.called - - directory.rmtree_p() - - def test_install(self): - """ Test the install method that it creates the correct links. """ - directory = Path('/tmp/kubernetes_installer_test/install') - ki = self.makeone('ppc64le', '1.2.3', directory) - assert not directory.exists(), 'The %s directory exits!' % directory - directory.makedirs_p() - # Create the files for the install method to link to. - (directory / 'kube-apiserver').touch() - (directory / 'kube-controller-manager').touch() - (directory / 'kube-proxy').touch() - (directory / 'kube-scheduler').touch() - (directory / 'kubectl').touch() - (directory / 'kubelet').touch() - - results = directory / 'install/results/go/here' - assert not results.exists() - ki.install(results) - assert results.isdir() - # Check that all the files were correctly aliased and are links. - assert (results / 'apiserver').islink() - assert (results / 'controller-manager').islink() - assert (results / 'kube-proxy').islink() - assert (results / 'scheduler').islink() - assert (results / 'kubectl').islink() - assert (results / 'kubelet').islink() - - directory.rmtree_p() diff --git a/cluster/juju/charms/trusty/kubernetes-master/unit_tests/test_install.py b/cluster/juju/charms/trusty/kubernetes-master/unit_tests/test_install.py deleted file mode 100644 index 2f79a4c216a..00000000000 --- a/cluster/juju/charms/trusty/kubernetes-master/unit_tests/test_install.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from mock import patch, Mock, MagicMock -from path import Path -import pytest -import sys - -# Munge the python path so we can find our hook code -d = Path('__file__').parent.abspath() / 'hooks' -sys.path.insert(0, d.abspath()) - -# Import the modules from the hook -import install - - -class TestInstallHook(): - - @patch('install.Path') - def test_update_rc_files(self, pmock): - """ - Test happy path on updating env files. Assuming everything - exists and is in place. - """ - pmock.return_value.lines.return_value = ['line1', 'line2'] - install.update_rc_files(['test1', 'test2']) - pmock.return_value.write_lines.assert_called_with(['line1', 'line2', - 'test1', 'test2']) - - def test_update_rc_files_with_nonexistent_path(self): - """ - Test an unhappy path if the bashrc/users do not exist. - """ - p = [Path('/home/deadbeefdoesnotexist/.bashrc')] - with pytest.raises(OSError) as exinfo: - install.update_rc_files(['test1', 'test2'], rc_files=p) - - @patch('install.fetch') - @patch('install.hookenv') - def test_package_installation(self, hemock, ftmock): - """ - Verify we are calling the known essentials to build and syndicate - kubes. - """ - pkgs = ['apache2-utils', - 'build-essential', - 'docker.io', - 'git', - 'make', - 'nginx', - 'python-pip',] - install.install_packages() - hemock.log.assert_called_with('Installing Debian packages') - ftmock.filter_installed_packages.assert_called_with(pkgs) - - @patch('install.archiveurl.ArchiveUrlFetchHandler') - def test_go_download(self, aumock): - """ - Test that we are actually handing off to charm-helpers to - download a specific archive of Go. This is non-configurable so - its reasonably safe to assume we're going to always do this, - and when it changes we shall curse the brittleness of this test. - """ - ins_mock = aumock.return_value.install - install.download_go() - url = 'https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz' # noqa - sha1 = '5020af94b52b65cc9b6f11d50a67e4bae07b0aff' - ins_mock.assert_called_with(url, '/usr/local', sha1, 'sha1') - - @patch('install.subprocess') - def test_clone_repository(self, spmock): - """ - We're not using a unit-tested git library - so ensure our subprocess - call is consistent. If we change this, we want to know we've broken it. - """ - install.clone_repository() - repo = 'https://github.com/kubernetes/kubernetes.git' - direct = '/opt/kubernetes' - spmock.check_output.assert_called_with(['git', 'clone', repo, direct]) - - @patch('install.install_packages') - @patch('install.download_go') - @patch('install.clone_repository') - @patch('install.update_rc_files') - @patch('install.Path') - @patch('install.hookenv') - def test_install_main(self, hemock, pmock, urmock, crmock, dgmock, ipmock): - """ - Ensure the driver/main method is calling all the supporting methods. - """ - install.install() - - assert(ipmock.called) - assert(dgmock.called) - assert(crmock.called) - assert(urmock.called) - - assert(pmock.called) - pmock.assert_called_with('/srv/kubernetes') - - hemock.open_port.assert_any_call(443) - hemock.open_port.assert_any_call(8080) - hemock.open_port.assert_any_call(6443) diff --git a/cluster/juju/charms/trusty/kubernetes/.bzrignore b/cluster/juju/charms/trusty/kubernetes/.bzrignore deleted file mode 100644 index 6b8710a711f..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/.bzrignore +++ /dev/null @@ -1 +0,0 @@ -.git diff --git a/cluster/juju/charms/trusty/kubernetes/.gitignore b/cluster/juju/charms/trusty/kubernetes/.gitignore deleted file mode 100644 index f42003421e2..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.bzr -*.pyc -*~ -*\#* -/files/.kubernetes-* -.venv diff --git a/cluster/juju/charms/trusty/kubernetes/.vendor-rc b/cluster/juju/charms/trusty/kubernetes/.vendor-rc deleted file mode 100644 index 87619d5117c..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/.vendor-rc +++ /dev/null @@ -1,5 +0,0 @@ -omit: -- .git -- .gitignore -- .gitmodules -- revision diff --git a/cluster/juju/charms/trusty/kubernetes/Makefile b/cluster/juju/charms/trusty/kubernetes/Makefile deleted file mode 100644 index 4cb969f3f43..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/Makefile +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2016 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -build: virtualenv lint test - -virtualenv: - virtualenv .venv - .venv/bin/pip install -q -r requirements.txt - -lint: virtualenv - @.venv/bin/flake8 hooks --exclude=charmhelpers --ignore=W391 - @.venv/bin/charm proof - -test: virtualenv - @CHARM_DIR=. PYTHONPATH=./hooks .venv/bin/py.test unit_tests/* - -functional-test: - @bundletester - -release: check-path virtualenv - @.venv/bin/pip install git-vendor - @.venv/bin/git-vendor sync -d ${KUBERNETES_BZR} - -check-path: -ifndef KUBERNETES_BZR - $(error KUBERNETES_BZR is undefined) -endif - -clean: - rm -rf .venv - find -name *.pyc -delete - rm -rf unit_tests/.cache diff --git a/cluster/juju/charms/trusty/kubernetes/README.md b/cluster/juju/charms/trusty/kubernetes/README.md deleted file mode 100644 index bc9f7a07440..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# Kubernetes Node Charm - -[Kubernetes](https://github.com/kubernetes/kubernetes) is an open -source system for managing containerized applications across multiple hosts. -Kubernetes uses [Docker](http://www.docker.io/) to package, instantiate and run -containerized applications. - -The Kubernetes Juju charms enable you to run Kubernetes on all the cloud -platforms that Juju supports. - -A Kubernetes deployment consists of several independent charms that can be -scaled to meet your needs - -### Etcd -Etcd is a key value store for Kubernetes. All persistent master state -is stored in `etcd`. - -### Flannel-docker -Flannel is a -[software defined networking](http://en.wikipedia.org/wiki/Software-defined_networking) -component that provides individual subnets for each machine in the cluster. - -### Docker -Docker is an open platform for distributing applications for system administrators. - -### Kubernetes master -The controlling unit in a Kubernetes cluster is called the master. It is the -main management contact point providing many management services for the worker -nodes. - -### Kubernetes node -The servers that perform the work are known as nodes (previously minions). -Nodes must be able to -communicate with the master and run the workloads that are assigned to them. - - -## Usage - -#### Deploying the Development Focus - -To deploy a Kubernetes environment in Juju : - - juju deploy cs:~kubernetes/trusty/etcd - juju deploy cs:trusty/flannel-docker - juju deploy cs:trusty/docker - juju deploy local:trusty/kubernetes-master - juju deploy local:trusty/kubernetes - - juju add-relation etcd flannel-docker - juju add-relation flannel-docker:network docker:network - juju add-relation flannel-docker:docker-host docker - juju add-relation etcd kubernetes - juju add-relation etcd kubernetes-master - juju add-relation kubernetes kubernetes-master - - -#### Deploying the recommended configuration - -Use the 'juju quickstart' command to deploy a Kubernetes cluster to any cloud -supported by Juju. - -The charm store version of the Kubernetes bundle can be deployed as follows: - - juju quickstart u/kubernetes/kubernetes-cluster - -> Note: The charm store bundle may be locked to a specific Kubernetes release. - -Alternately you could deploy a Kubernetes bundle straight from github or a file: - - juju quickstart https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/juju/bundles/local.yaml - -The command above does few things for you: - -- Starts a curses based gui for managing your cloud or MAAS credentials -- Looks for a bootstrapped deployment environment, and bootstraps if - required. This will launch a bootstrap node in your chosen - deployment environment (machine 0). -- Deploys the Juju GUI to your environment onto the bootstrap node. -- Provisions 4 machines, and deploys the Kubernetes services on top of - them (Kubernetes-master, two Kubernetes nodes using flannel, and etcd). -- Orchestrates the relations among the services, and exits. - -Now you should have a running Kubernetes. Run `juju status ---format=oneline` to see the address of your kubernetes-master unit. - - -#### Post Deployment - -To interact with the kubernetes environment, either build or -[download](https://github.com/kubernetes/kubernetes/releases) the -[kubectl](https://github.com/kubernetes/kubernetes/blob/master/docs/user-guide/kubectl/kubectl.md) -binary (available in the releases binary tarball) and point it to the master with : - - - $ juju status kubernetes-master | grep public - public-address: 104.131.108.99 - $ export KUBERNETES_MASTER="104.131.108.99" - -# Configuration -For you convenience this charm supports changing the version of the Kubernetes -release through a configuration option. -This can be done through the Juju GUI or on the command line: - - juju set kubernetes version=”v0.10.0” - -If the charm does not already contain the tar file with the desired architecture -and version it will attempt to download the kubernetes binaries using the gsutil -command. - -Congratulations you know have deployed a Kubernetes environment! Use the -[kubectl](https://github.com/kubernetes/kubernetes/blob/master/docs/user-guide/kubectl/kubectl.md) -to interact with the environment. - -# Kubernetes information - -- [Kubernetes github project](https://github.com/kubernetes/kubernetes) -- [Kubernetes issue tracker](https://github.com/kubernetes/kubernetes/issues) -- [Kubernetes Documenation](https://github.com/kubernetes/kubernetes/tree/master/docs) -- [Kubernetes releases](https://github.com/kubernetes/kubernetes/releases) - - -[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/cluster/juju/charms/trusty/kubernetes/README.md?pixel)]() diff --git a/cluster/juju/charms/trusty/kubernetes/copyright b/cluster/juju/charms/trusty/kubernetes/copyright deleted file mode 100644 index a0b409a8e84..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/copyright +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/cluster/juju/charms/trusty/kubernetes/files/cadvisor.upstart.tmpl b/cluster/juju/charms/trusty/kubernetes/files/cadvisor.upstart.tmpl deleted file mode 100644 index f142ca868f3..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/files/cadvisor.upstart.tmpl +++ /dev/null @@ -1,16 +0,0 @@ -description "cadvisor container metrics" - -start on started docker -stop on stopping docker - -limit nofile 20000 20000 - -kill timeout 60 # wait 60s between SIGTERM and SIGKILL. - -exec docker run \ - --volume=/var/run:/var/run:rw \ - --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro \ - --volume=/var/lib/docker/:/var/lib/docker:ro \ - --publish=127.0.0.1:4193:8080 \ - --name=cadvisor \ - google/cadvisor:latest diff --git a/cluster/juju/charms/trusty/kubernetes/files/kubelet.upstart.tmpl b/cluster/juju/charms/trusty/kubernetes/files/kubelet.upstart.tmpl deleted file mode 100644 index d2866fe625e..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/files/kubelet.upstart.tmpl +++ /dev/null @@ -1,16 +0,0 @@ -description "kubernetes kubelet" - -start on runlevel [2345] -stop on runlevel [!2345] - -limit nofile 20000 20000 - -kill timeout 60 # wait 60s between SIGTERM and SIGKILL. - -exec /usr/local/bin/kubelet \ - --address=%(kubelet_bind_addr)s \ - --allow-privileged=true \ - --api-servers=%(kubeapi_server)s \ - --hostname-override=%(kubelet_bind_addr)s \ - --cadvisor-port=4193 \ - --logtostderr=true diff --git a/cluster/juju/charms/trusty/kubernetes/files/proxy.upstart.tmpl b/cluster/juju/charms/trusty/kubernetes/files/proxy.upstart.tmpl deleted file mode 100644 index 81a07f38a18..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/files/proxy.upstart.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -description "kubernetes proxy" - -start on runlevel [2345] -stop on runlevel [!2345] - -limit nofile 20000 20000 - -kill timeout 60 # wait 60s between SIGTERM and SIGKILL. - -exec /usr/local/bin/proxy \ - --master=%(kubeapi_server)s \ - --logtostderr=true \ - --hostname-override=%(kubelet_bind_addr)s diff --git a/cluster/juju/charms/trusty/kubernetes/hooks/api-relation-changed b/cluster/juju/charms/trusty/kubernetes/hooks/api-relation-changed deleted file mode 120000 index 9416ca6ac28..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/hooks/api-relation-changed +++ /dev/null @@ -1 +0,0 @@ -hooks.py \ No newline at end of file diff --git a/cluster/juju/charms/trusty/kubernetes/hooks/etcd-relation-changed b/cluster/juju/charms/trusty/kubernetes/hooks/etcd-relation-changed deleted file mode 120000 index 9416ca6ac28..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/hooks/etcd-relation-changed +++ /dev/null @@ -1 +0,0 @@ -hooks.py \ No newline at end of file diff --git a/cluster/juju/charms/trusty/kubernetes/hooks/hooks.py b/cluster/juju/charms/trusty/kubernetes/hooks/hooks.py deleted file mode 100755 index 09e5b906940..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/hooks/hooks.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -The main hook file that is called by Juju. -""" -import os -import socket -import subprocess -import sys -import urlparse - -from charmhelpers.core import hookenv, host -from kubernetes_installer import KubernetesInstaller -from path import Path - -from lib.registrator import Registrator - -hooks = hookenv.Hooks() - - -@hooks.hook('api-relation-changed') -def api_relation_changed(): - """ - On the relation to the api server, this function determines the appropriate - architecture and the configured version to copy the kubernetes binary files - from the kubernetes-master charm and installs it locally on this machine. - """ - hookenv.log('Starting api-relation-changed') - charm_dir = Path(hookenv.charm_dir()) - # Get the package architecture, rather than the from the kernel (uname -m). - arch = subprocess.check_output(['dpkg', '--print-architecture']).strip() - kubernetes_bin_dir = Path('/opt/kubernetes/bin') - # Get the version of kubernetes to install. - version = subprocess.check_output(['relation-get', 'version']).strip() - print('Relation version: ', version) - if not version: - print('No version present in the relation.') - exit(0) - version_file = charm_dir / '.version' - if version_file.exists(): - previous_version = version_file.text() - print('Previous version: ', previous_version) - if version == previous_version: - exit(0) - # Can not download binaries while the service is running, so stop it. - # TODO: Figure out a better way to handle upgraded kubernetes binaries. - for service in ('kubelet', 'proxy'): - if host.service_running(service): - host.service_stop(service) - command = ['relation-get', 'private-address'] - # Get the kubernetes-master address. - server = subprocess.check_output(command).strip() - print('Kubernetes master private address: ', server) - installer = KubernetesInstaller(arch, version, server, kubernetes_bin_dir) - installer.download() - installer.install() - # Write the most recently installed version number to the file. - version_file.write_text(version) - relation_changed() - - -@hooks.hook('etcd-relation-changed', - 'network-relation-changed') -def relation_changed(): - """Connect the parts and go :-) - """ - template_data = get_template_data() - - # Check required keys - for k in ('etcd_servers', 'kubeapi_server'): - if not template_data.get(k): - print('Missing data for %s %s' % (k, template_data)) - return - print('Running with\n%s' % template_data) - - # Setup kubernetes supplemental group - setup_kubernetes_group() - - # Register upstart managed services - for n in ('kubelet', 'proxy'): - if render_upstart(n, template_data) or not host.service_running(n): - print('Starting %s' % n) - host.service_restart(n) - - # Register machine via api - print('Registering machine') - register_machine(template_data['kubeapi_server']) - - # Save the marker (for restarts to detect prev install) - template_data.save() - - -def get_template_data(): - rels = hookenv.relations() - template_data = hookenv.Config() - template_data.CONFIG_FILE_NAME = '.unit-state' - - overlay_type = get_scoped_rel_attr('network', rels, 'overlay_type') - etcd_servers = get_rel_hosts('etcd', rels, ('hostname', 'port')) - api_servers = get_rel_hosts('api', rels, ('hostname', 'port')) - - # kubernetes master isn't ha yet. - if api_servers: - api_info = api_servers.pop() - api_servers = 'http://%s:%s' % (api_info[0], api_info[1]) - - template_data['overlay_type'] = overlay_type - template_data['kubelet_bind_addr'] = _bind_addr( - hookenv.unit_private_ip()) - template_data['proxy_bind_addr'] = _bind_addr( - hookenv.unit_get('public-address')) - template_data['kubeapi_server'] = api_servers - template_data['etcd_servers'] = ','.join([ - 'http://%s:%s' % (s[0], s[1]) for s in sorted(etcd_servers)]) - template_data['identifier'] = os.environ['JUJU_UNIT_NAME'].replace( - '/', '-') - return _encode(template_data) - - -def _bind_addr(addr): - if addr.replace('.', '').isdigit(): - return addr - try: - return socket.gethostbyname(addr) - except socket.error: - raise ValueError('Could not resolve private address') - - -def _encode(d): - for k, v in d.items(): - if isinstance(v, unicode): - d[k] = v.encode('utf8') - return d - - -def get_scoped_rel_attr(rel_name, rels, attr): - private_ip = hookenv.unit_private_ip() - for r, data in rels.get(rel_name, {}).items(): - for unit_id, unit_data in data.items(): - if unit_data.get('private-address') != private_ip: - continue - if unit_data.get(attr): - return unit_data.get(attr) - - -def get_rel_hosts(rel_name, rels, keys=('private-address',)): - hosts = [] - for r, data in rels.get(rel_name, {}).items(): - for unit_id, unit_data in data.items(): - if unit_id == hookenv.local_unit(): - continue - values = [unit_data.get(k) for k in keys] - if not all(values): - continue - hosts.append(len(values) == 1 and values[0] or values) - return hosts - - -def render_upstart(name, data): - tmpl_path = os.path.join( - os.environ.get('CHARM_DIR'), 'files', '%s.upstart.tmpl' % name) - - with open(tmpl_path) as fh: - tmpl = fh.read() - rendered = tmpl % data - - tgt_path = '/etc/init/%s.conf' % name - - if os.path.exists(tgt_path): - with open(tgt_path) as fh: - contents = fh.read() - if contents == rendered: - return False - - with open(tgt_path, 'w') as fh: - fh.write(rendered) - return True - - -def register_machine(apiserver, retry=False): - parsed = urlparse.urlparse(apiserver) - # identity = hookenv.local_unit().replace('/', '-') - private_address = hookenv.unit_private_ip() - - with open('/proc/meminfo') as fh: - info = fh.readline() - mem = info.strip().split(':')[1].strip().split()[0] - cpus = os.sysconf('SC_NPROCESSORS_ONLN') - - # https://github.com/kubernetes/kubernetes/blob/master/docs/admin/node.md - registration_request = Registrator() - registration_request.data['kind'] = 'Node' - registration_request.data['id'] = private_address - registration_request.data['name'] = private_address - registration_request.data['metadata']['name'] = private_address - registration_request.data['spec']['capacity']['mem'] = mem + ' K' - registration_request.data['spec']['capacity']['cpu'] = cpus - registration_request.data['spec']['externalID'] = private_address - registration_request.data['status']['hostIP'] = private_address - - try: - response, result = registration_request.register(parsed.hostname, - parsed.port, - '/api/v1/nodes') - except socket.error: - hookenv.status_set('blocked', - 'Error communicating with Kubenetes Master') - return - - print(response) - - try: - registration_request.command_succeeded(response, result) - except ValueError: - # This happens when we have already registered - # for now this is OK - pass - - -def setup_kubernetes_group(): - output = subprocess.check_output(['groups', 'kubernetes']) - - # TODO: check group exists - if 'docker' not in output: - subprocess.check_output( - ['usermod', '-a', '-G', 'docker', 'kubernetes']) - - -if __name__ == '__main__': - hooks.execute(sys.argv) diff --git a/cluster/juju/charms/trusty/kubernetes/hooks/install b/cluster/juju/charms/trusty/kubernetes/hooks/install deleted file mode 100755 index ee5565702ae..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/hooks/install +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -set -ex - -# Install is guaranteed to run once per rootfs - -echo "Installing kubernetes-node on $JUJU_UNIT_NAME" - -apt-get update -qq -apt-get install -q -y \ - bridge-utils \ - python-dev \ - python-pip \ - wget - -pip install -r $CHARM_DIR/python_requirements.txt - -# Create the necessary kubernetes group. -groupadd --force kubernetes - -if grep -q "^kubernetes:" /etc/passwd; then - echo "The kubernetes user already exists!" -else - # Create the user when kubernetes does not exist. - useradd -d /var/lib/kubernetes \ - -g kubernetes \ - -s /sbin/nologin \ - --system \ - kubernetes -fi - -install -d -m 0744 -o kubernetes -g kubernetes /var/lib/kubernetes -install -d -m 0744 -o kubernetes -g kubernetes /etc/kubernetes/manifests diff --git a/cluster/juju/charms/trusty/kubernetes/hooks/kubernetes_installer.py b/cluster/juju/charms/trusty/kubernetes/hooks/kubernetes_installer.py deleted file mode 100644 index 4da4313dbd2..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/hooks/kubernetes_installer.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import subprocess -from path import Path - - -class KubernetesInstaller(): - """ - This class contains the logic needed to install kuberentes binary files. - """ - - def __init__(self, arch, version, master, output_dir): - """ Gather the required variables for the install. """ - # The kubernetes charm needs certain commands to be aliased. - self.aliases = {'kube-proxy': 'proxy', - 'kubelet': 'kubelet'} - self.arch = arch - self.version = version - self.master = master - self.output_dir = output_dir - - def download(self): - """ Download the kuberentes binaries from the kubernetes master. """ - url = 'http://{0}/kubernetes/{1}/local/bin/linux/{2}'.format( - self.master, self.version, self.arch) - if not self.output_dir.isdir(): - self.output_dir.makedirs_p() - - for key in self.aliases: - uri = '{0}/{1}'.format(url, key) - destination = self.output_dir / key - wget = 'wget -nv {0} -O {1}'.format(uri, destination) - print(wget) - output = subprocess.check_output(wget.split()) - print(output) - destination.chmod(0o755) - - def install(self, install_dir=Path('/usr/local/bin')): - """ Create links to the binary files to the install directory. """ - - if not install_dir.isdir(): - install_dir.makedirs_p() - - # Create the symbolic links to the real kubernetes binaries. - for key, value in self.aliases.iteritems(): - target = self.output_dir / key - if target.exists(): - link = install_dir / value - if link.exists(): - link.remove() - target.symlink(link) - else: - print('Error target file {0} does not exist.'.format(target)) - exit(1) diff --git a/cluster/juju/charms/trusty/kubernetes/hooks/lib/__init__.py b/cluster/juju/charms/trusty/kubernetes/hooks/lib/__init__.py deleted file mode 100644 index 8eefd60722b..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/hooks/lib/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/cluster/juju/charms/trusty/kubernetes/hooks/lib/registrator.py b/cluster/juju/charms/trusty/kubernetes/hooks/lib/registrator.py deleted file mode 100644 index 3db0b7e228c..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/hooks/lib/registrator.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import httplib -import json -import time - - -class Registrator: - - def __init__(self): - self.ds = { - "creationTimestamp": "", - "kind": "Node", - "name": "", # private_address - "metadata": { - "name": "", # private_address, - }, - "spec": { - "externalID": "", # private_address - "capacity": { - "mem": "", # mem + ' K', - "cpu": "", # cpus - } - }, - "status": { - "conditions": [], - "hostIP": "", # private_address - } - } - - @property - def data(self): - ''' Returns a data-structure for population to make a request. ''' - return self.ds - - def register(self, hostname, port, api_path): - ''' Contact the API Server for a new registration ''' - headers = {"Content-type": "application/json", - "Accept": "application/json"} - connection = httplib.HTTPConnection(hostname, port, timeout=12) - print 'CONN {}'.format(connection) - connection.request("POST", api_path, json.dumps(self.data), headers) - response = connection.getresponse() - body = response.read() - print(body) - result = json.loads(body) - print("Response status:%s reason:%s body:%s" % - (response.status, response.reason, result)) - return response, result - - def update(self): - ''' Contact the API Server to update a registration ''' - # do a get on the API for the node - # repost to the API with any modified data - pass - - def save(self): - ''' Marshall the registration data ''' - # TODO - pass - - def command_succeeded(self, response, result): - ''' Evaluate response data to determine if the command successful ''' - if response.status in [200, 201, 409]: - # The 409 response is when a unit is already registered. We do not - # have an update method above, so for now, accept whats in etcd and - # assume registered. - print("Registered") - return True - elif response.status in (500,) and result.get( - 'message', '').startswith('The requested resource does not exist'): # noqa - # There is something fishy in the kube api here (0.4 dev), first - # time to register a new node, we always seem to get this error. - # http://issue.k8s.io/1995 - time.sleep(1) - print("Retrying registration...") - raise ValueError("Registration returned 500, retry") - # return register_machine(apiserver, retry=True) - else: - print("Registration error") - # TODO - get request data - raise RuntimeError("Unable to register machine with") diff --git a/cluster/juju/charms/trusty/kubernetes/hooks/network-relation-changed b/cluster/juju/charms/trusty/kubernetes/hooks/network-relation-changed deleted file mode 120000 index 9416ca6ac28..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/hooks/network-relation-changed +++ /dev/null @@ -1 +0,0 @@ -hooks.py \ No newline at end of file diff --git a/cluster/juju/charms/trusty/kubernetes/hooks/start b/cluster/juju/charms/trusty/kubernetes/hooks/start deleted file mode 100755 index 9a5833242a0..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/hooks/start +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -ex - -# Start is guaranteed to be called once when after the unit is installed -# *AND* once every time a machine is rebooted. - -if [ ! -f $CHARM_DIR/.unit-state ] -then - exit 0; -fi - -service docker restart -service proxy restart -service kubelet restart diff --git a/cluster/juju/charms/trusty/kubernetes/icon.svg b/cluster/juju/charms/trusty/kubernetes/icon.svg deleted file mode 100644 index 55098d1079f..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/icon.svg +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/cluster/juju/charms/trusty/kubernetes/metadata.yaml b/cluster/juju/charms/trusty/kubernetes/metadata.yaml deleted file mode 100644 index 0de51dda336..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/metadata.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: kubernetes -summary: Container Cluster Management Node -maintainers: - - Matt Bruzek - - Whit Morriss - - Charles Butler -description: | - Provides a kubernetes node for running containers - See http://goo.gl/CSggxE -tags: - - ops - - network -subordinate: true -requires: - etcd: - interface: etcd - api: - interface: kubernetes-api - network: - interface: overlay-network - docker-host: - interface: juju-info - scope: container diff --git a/cluster/juju/charms/trusty/kubernetes/python_requirements.txt b/cluster/juju/charms/trusty/kubernetes/python_requirements.txt deleted file mode 100644 index 09375e79b58..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/python_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -path.py diff --git a/cluster/juju/charms/trusty/kubernetes/requirements.txt b/cluster/juju/charms/trusty/kubernetes/requirements.txt deleted file mode 100644 index aaf7acb8996..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -flake8 -pytest -bundletester -path.py diff --git a/cluster/juju/charms/trusty/kubernetes/unit_tests/lib/test_registrator.py b/cluster/juju/charms/trusty/kubernetes/unit_tests/lib/test_registrator.py deleted file mode 100644 index 57f426ad429..00000000000 --- a/cluster/juju/charms/trusty/kubernetes/unit_tests/lib/test_registrator.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2015 The Kubernetes Authors All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import json -from mock import MagicMock, patch -from path import Path -import pytest -import sys - -d = Path('__file__').parent.abspath() / 'hooks' -sys.path.insert(0, d.abspath()) - -from lib.registrator import Registrator - - -class TestRegistrator(): - - def setup_method(self, method): - self.r = Registrator() - - def test_data_type(self): - if type(self.r.data) is not dict: - pytest.fail("Invalid type") - - @patch('json.loads') - @patch('httplib.HTTPConnection') - def test_register(self, httplibmock, jsonmock): - self.r.register('foo', 80, '/v1/test') - - httplibmock.assert_called_with('foo', 80, timeout=12) - requestmock = httplibmock().request - requestmock.assert_called_with( - "POST", "/v1/test", - json.dumps(self.r.data), - {"Content-type": "application/json", - "Accept": "application/json"}) - - def test_command_succeeded(self): - response = MagicMock() - result = json.loads('{"status": "Failure", "kind": "Status", "code": 409, "apiVersion": "v1", "reason": "AlreadyExists", "details": {"kind": "node", "name": "10.200.147.200"}, "message": "node \\"10.200.147.200\\" already exists", "creationTimestamp": null}') # noqa - response.status = 200 - self.r.command_succeeded(response, result) - response.status = 409 - self.r.command_succeeded(response, result) - response.status = 500 - with pytest.raises(RuntimeError): - self.r.command_succeeded(response, result) diff --git a/cluster/juju/charms/trusty/kubernetes/unit_tests/test_hooks.py b/cluster/juju/identify-leaders.py old mode 100644 new mode 100755 similarity index 54% rename from cluster/juju/charms/trusty/kubernetes/unit_tests/test_hooks.py rename to cluster/juju/identify-leaders.py index d1d17b4f176..6d6244b751a --- a/cluster/juju/charms/trusty/kubernetes/unit_tests/test_hooks.py +++ b/cluster/juju/identify-leaders.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2015 The Kubernetes Authors All rights reserved. +# Copyright 2016 The Kubernetes Authors All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,11 +14,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -# import pytest +from subprocess import check_output +import yaml - -class TestHooks(): - - # TODO: Actually write tests. - def test_fake(self): - pass +out = check_output(['juju', 'status', 'kubernetes', '--format=yaml']) +try: + parsed_output = yaml.safe_load(out) + model = parsed_output['services']['kubernetes']['units'] + for unit in model: + if 'workload-status' in model[unit].keys(): + if 'leader' in model[unit]['workload-status']['message']: + print(unit) +except: + pass diff --git a/cluster/juju/layers/kubernetes/README.md b/cluster/juju/layers/kubernetes/README.md new file mode 100644 index 00000000000..bd34e55b0a8 --- /dev/null +++ b/cluster/juju/layers/kubernetes/README.md @@ -0,0 +1,77 @@ +# kubernetes + +[Kubernetes](https://github.com/kubernetes/kubernetes) is an open +source system for managing application containers across multiple hosts. +This version of Kubernetes uses [Docker](http://www.docker.io/) to package, +instantiate and run containerized applications. + +This charm is an encapsulation of the +[Running Kubernetes locally via +Docker](https://github.com/kubernetes/kubernetes/blob/master/docs/getting-started-guides/docker.md) +document. The released hyperkube image (`gcr.io/google_containers/hyperkube`) +is currently pulled from a [Google owned container repository +repository](https://cloud.google.com/container-registry/). For this charm to +work it will need access to the repository to `docker pull` the images. + +This charm was built from other charm layers using the reactive framework. The +`layer:docker` is the base layer. For more information please read [Getting +Started Developing charms](https://jujucharms.com/docs/devel/developer-getting-started) + +# Deployment +The kubernetes charms require a relation to a distributed key value store +(ETCD) which Kubernetes uses for persistent storage of all of its REST API +objects. + +``` +juju deploy trusty/etcd +juju deploy local:trusty/kubernetes +juju add-relation kubernetes etcd +``` + +# Configuration +For your convenience this charm supports some configuration options to set up +a Kuberentes cluster that works in your environment: + +**version**: Set the version of the Kubernetes containers to deploy. +The default value is "v1.0.6". Changing the version causes the all the +Kubernetes containers to be restarted. + +**cidr**: Set the IP range for the Kubernetes cluster. eg: 10.1.0.0/16 + + +## State Events +While this charm is meant to be a top layer, it can be used to build other +solutions. This charm sets or removes states from the reactive framework that +other layers could react appropriately. The states that other layers would be +interested in are as follows: + +**kubelet.available** - The hyperkube container has been run with the kubelet +service and configuration that started the apiserver, controller-manager and +scheduler containers. + +**proxy.available** - The hyperkube container has been run with the proxy +service and configuration that handles Kubernetes networking. + +**kubectl.package.created** - Indicates the availability of the `kubectl` +application along with the configuration needed to contact the cluster +securely. You will need to download the `/home/ubuntu/kubectl_package.tar.gz` +from the kubernetes leader unit to your machine so you can control the cluster. + +**skydns.available** - Indicates when the Domain Name System (DNS) for the +cluster is operational. + + +# Kubernetes information + + - [Kubernetes github project](https://github.com/kubernetes/kubernetes) + - [Kubernetes issue tracker](https://github.com/kubernetes/kubernetes/issues) + - [Kubernetes Documenation](http://kubernetes.io/docs/) + - [Kubernetes releases](https://github.com/kubernetes/kubernetes/releases) + +# Contact + + * Charm Author: Matthew Bruzek <Matthew.Bruzek@canonical.com> + * Charm Contributor: Charles Butler <Charles.Butler@canonical.com> + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/cluster/juju/layers/kubernetes/README.md?pixel)]() diff --git a/cluster/juju/layers/kubernetes/actions.yaml b/cluster/juju/layers/kubernetes/actions.yaml new file mode 100644 index 00000000000..82cfd5ac646 --- /dev/null +++ b/cluster/juju/layers/kubernetes/actions.yaml @@ -0,0 +1,2 @@ +guestbook-example: + description: Launch the guestbook example in your k8s cluster diff --git a/cluster/juju/layers/kubernetes/actions/guestbook-example b/cluster/juju/layers/kubernetes/actions/guestbook-example new file mode 100755 index 00000000000..b2af79a4351 --- /dev/null +++ b/cluster/juju/layers/kubernetes/actions/guestbook-example @@ -0,0 +1,35 @@ +#!/bin/bash + +# Copyright 2016 The Kubernetes Authors All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Launch the Guestbook example in Kubernetes. This will use the pod and service +# definitions from `files/guestbook-example/*.yaml` to launch a leader/follower +# redis cluster, with a web-front end to collect user data and store in redis. +# This example app can easily scale across multiple nodes, and exercises the +# networking, pod creation/scale, service definition, and replica controller of +# kubernetes. +# +# Lifted from github.com/kubernetes/kubernetes/examples/guestbook-example + +set -e + +if [ ! -d files/guestbook-example ]; then + mkdir -p files/guestbook-example + curl -o $CHARM_DIR/files/guestbook-example/guestbook-all-in-one.yaml https://raw.githubusercontent.com/kubernetes/kubernetes/master/examples/guestbook/all-in-one/guestbook-all-in-one.yaml +fi + +kubectl create -f files/guestbook-example/guestbook-all-in-one.yaml + diff --git a/cluster/juju/layers/kubernetes/config.yaml b/cluster/juju/layers/kubernetes/config.yaml new file mode 100644 index 00000000000..1c221b124ed --- /dev/null +++ b/cluster/juju/layers/kubernetes/config.yaml @@ -0,0 +1,14 @@ +options: + version: + type: string + default: "v1.1.7" + description: | + The version of Kubernetes to use in this charm. The version is + inserted in the configuration files that specify the hyperkube + container to use when starting a Kubernetes cluster. Changing this + value will restart the Kubernetes cluster. + cidr: + type: string + default: 10.1.0.0/16 + description: | + Network CIDR to assign to K8s service groups diff --git a/cluster/juju/charms/trusty/kubernetes-master/icon.svg b/cluster/juju/layers/kubernetes/icon.svg similarity index 100% rename from cluster/juju/charms/trusty/kubernetes-master/icon.svg rename to cluster/juju/layers/kubernetes/icon.svg diff --git a/cluster/juju/layers/kubernetes/layer.yaml b/cluster/juju/layers/kubernetes/layer.yaml new file mode 100644 index 00000000000..dfdeebdcec9 --- /dev/null +++ b/cluster/juju/layers/kubernetes/layer.yaml @@ -0,0 +1 @@ +includes: ['layer:docker', 'layer:flannel', 'layer:tls', 'interface:etcd'] diff --git a/cluster/juju/layers/kubernetes/metadata.yaml b/cluster/juju/layers/kubernetes/metadata.yaml new file mode 100644 index 00000000000..cd83a08c08c --- /dev/null +++ b/cluster/juju/layers/kubernetes/metadata.yaml @@ -0,0 +1,17 @@ +name: kubernetes +summary: Kubernetes is an application container orchestration platform. +maintainers: + - Matthew Bruzek + - Charles Butler +description: | + Kubernetes is an open-source platform for deplying, scaling, and operations + of appliation containers across a cluster of hosts. Kubernetes is portable + in that it works with public, private, and hybrid clouds. Extensible through + a pluggable infrastructure. Self healing in that it will automatically + restart and place containers on healthy nodes if a node ever goes away. +tags: + - infrastructure +subordinate: false +requires: + etcd: + interface: etcd diff --git a/cluster/juju/layers/kubernetes/reactive/k8s.py b/cluster/juju/layers/kubernetes/reactive/k8s.py new file mode 100644 index 00000000000..cad4b00ae8e --- /dev/null +++ b/cluster/juju/layers/kubernetes/reactive/k8s.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python + +# Copyright 2015 The Kubernetes Authors All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from shlex import split +from shutil import copy2 +from subprocess import check_call + +from charms.docker.compose import Compose +from charms.reactive import hook +from charms.reactive import remove_state +from charms.reactive import set_state +from charms.reactive import when +from charms.reactive import when_not + +from charmhelpers.core import hookenv +from charmhelpers.core.hookenv import is_leader +from charmhelpers.core.hookenv import status_set +from charmhelpers.core.templating import render +from charmhelpers.core import unitdata +from charmhelpers.core.host import chdir +from contextlib import contextmanager + + +@hook('config-changed') +def config_changed(): + '''If the configuration values change, remove the available states.''' + config = hookenv.config() + if any(config.changed(key) for key in config.keys()): + hookenv.log('Configuration options have changed.') + # Use the Compose class that encapsulates the docker-compose commands. + compose = Compose('files/kubernetes') + hookenv.log('Removing kubelet container and kubelet.available state.') + # Stop and remove the Kubernetes kubelet container.. + compose.kill('kubelet') + compose.rm('kubelet') + # Remove the state so the code can react to restarting kubelet. + remove_state('kubelet.available') + hookenv.log('Removing proxy container and proxy.available state.') + # Stop and remove the Kubernetes proxy container. + compose.kill('proxy') + compose.rm('proxy') + # Remove the state so the code can react to restarting proxy. + remove_state('proxy.available') + + if config.changed('version'): + hookenv.log('Removing kubectl.downloaded state so the new version' + ' of kubectl will be downloaded.') + remove_state('kubectl.downloaded') + + +@when('tls.server.certificate available') +@when_not('k8s.server.certificate available') +def server_cert(): + '''When the server certificate is available, get the server certificate from + the charm unit data and write it to the proper directory. ''' + destination_directory = '/srv/kubernetes' + # Save the server certificate from unitdata to /srv/kubernetes/server.crt + save_certificate(destination_directory, 'server') + # Copy the unitname.key to /srv/kubernetes/server.key + copy_key(destination_directory, 'server') + set_state('k8s.server.certificate available') + + +@when('tls.client.certificate available') +@when_not('k8s.client.certficate available') +def client_cert(): + '''When the client certificate is available, get the client certificate + from the charm unitdata and write it to the proper directory. ''' + destination_directory = '/srv/kubernetes' + if not os.path.isdir(destination_directory): + os.makedirs(destination_directory) + os.chmod(destination_directory, 0o770) + # The client certificate is also available on charm unitdata. + client_cert_path = 'easy-rsa/easyrsa3/pki/issued/client.crt' + kube_cert_path = os.path.join(destination_directory, 'client.crt') + if os.path.isfile(client_cert_path): + # Copy the client.crt to /srv/kubernetes/client.crt + copy2(client_cert_path, kube_cert_path) + # The client key is only available on the leader. + client_key_path = 'easy-rsa/easyrsa3/pki/private/client.key' + kube_key_path = os.path.join(destination_directory, 'client.key') + if os.path.isfile(client_key_path): + # Copy the client.key to /srv/kubernetes/client.key + copy2(client_key_path, kube_key_path) + + +@when('tls.certificate.authority available') +@when_not('k8s.certificate.authority available') +def ca(): + '''When the Certificate Authority is available, copy the CA from the + /usr/local/share/ca-certificates/k8s.crt to the proper directory. ''' + # Ensure the /srv/kubernetes directory exists. + directory = '/srv/kubernetes' + if not os.path.isdir(directory): + os.makedirs(directory) + os.chmod(directory, 0o770) + # Normally the CA is just on the leader, but the tls layer installs the + # CA on all systems in the /usr/local/share/ca-certificates directory. + ca_path = '/usr/local/share/ca-certificates/{0}.crt'.format( + hookenv.service_name()) + # The CA should be copied to the destination directory and named 'ca.crt'. + destination_ca_path = os.path.join(directory, 'ca.crt') + if os.path.isfile(ca_path): + copy2(ca_path, destination_ca_path) + set_state('k8s.certificate.authority available') + + +@when('kubelet.available', 'proxy.available', 'cadvisor.available') +def final_messaging(): + '''Lower layers emit messages, and if we do not clear the status messaging + queue, we are left with whatever the last method call sets status to. ''' + # It's good UX to have consistent messaging that the cluster is online + if is_leader(): + status_set('active', 'Kubernetes leader running') + else: + status_set('active', 'Kubernetes follower running') + + +@when('kubelet.available', 'proxy.available', 'cadvisor.available') +@when_not('skydns.available') +def launch_skydns(): + '''Create a kubernetes service and resource controller for the skydns + service. ''' + # Only launch and track this state on the leader. + # Launching duplicate SkyDNS rc will raise an error + if not is_leader(): + return + cmd = "kubectl create -f files/manifests/skydns-rc.yml" + check_call(split(cmd)) + cmd = "kubectl create -f files/manifests/skydns-svc.yml" + check_call(split(cmd)) + set_state('skydns.available') + + +@when('docker.available') +@when_not('etcd.available') +def relation_message(): + '''Take over messaging to let the user know they are pending a relationship + to the ETCD cluster before going any further. ''' + status_set('waiting', 'Waiting for relation to ETCD') + + +@when('etcd.available', 'tls.server.certificate available') +@when_not('kubelet.available', 'proxy.available') +def master(etcd): + '''Install and run the hyperkube container that starts kubernetes-master. + This actually runs the kubelet, which in turn runs a pod that contains the + other master components. ''' + render_files(etcd) + # Use the Compose class that encapsulates the docker-compose commands. + compose = Compose('files/kubernetes') + status_set('maintenance', 'Starting the Kubernetes kubelet container.') + # Start the Kubernetes kubelet container using docker-compose. + compose.up('kubelet') + set_state('kubelet.available') + # Open the secure port for api-server. + hookenv.open_port(6443) + status_set('maintenance', 'Starting the Kubernetes proxy container') + # Start the Kubernetes proxy container using docker-compose. + compose.up('proxy') + set_state('proxy.available') + status_set('active', 'Kubernetes started') + + +@when('proxy.available') +@when_not('kubectl.downloaded') +def download_kubectl(): + '''Download the kubectl binary to test and interact with the cluster.''' + status_set('maintenance', 'Downloading the kubectl binary') + version = hookenv.config()['version'] + cmd = 'wget -nv -O /usr/local/bin/kubectl https://storage.googleapis.com/' \ + 'kubernetes-release/release/{0}/bin/linux/amd64/kubectl' + cmd = cmd.format(version) + hookenv.log('Downloading kubelet: {0}'.format(cmd)) + check_call(split(cmd)) + cmd = 'chmod +x /usr/local/bin/kubectl' + check_call(split(cmd)) + set_state('kubectl.downloaded') + status_set('active', 'Kubernetes installed') + + +@when('kubectl.downloaded') +@when_not('kubectl.package.created') +def package_kubectl(): + '''Package the kubectl binary and configuration to a tar file for users + to consume and interact directly with Kubernetes.''' + if not is_leader(): + return + context = 'default-context' + cluster_name = 'kubernetes' + public_address = hookenv.unit_public_ip() + directory = '/srv/kubernetes' + key = 'client.key' + ca = 'ca.crt' + cert = 'client.crt' + user = 'ubuntu' + port = '6443' + with chdir(directory): + # Create the config file with the external address for this server. + cmd = 'kubectl config set-cluster --kubeconfig={0}/config {1} ' \ + '--server=https://{2}:{3} --certificate-authority={4}' + check_call(split(cmd.format(directory, cluster_name, public_address, + port, ca))) + # Create the credentials. + cmd = 'kubectl config set-credentials --kubeconfig={0}/config {1} ' \ + '--client-key={2} --client-certificate={3}' + check_call(split(cmd.format(directory, user, key, cert))) + # Create a default context with the cluster. + cmd = 'kubectl config set-context --kubeconfig={0}/config {1}' \ + ' --cluster={2} --user={3}' + check_call(split(cmd.format(directory, context, cluster_name, user))) + # Now make the config use this new context. + cmd = 'kubectl config use-context --kubeconfig={0}/config {1}' + check_call(split(cmd.format(directory, context))) + # Copy the kubectl binary to this directory + cmd = 'cp -v /usr/local/bin/kubectl {0}'.format(directory) + check_call(split(cmd)) + + # Create an archive with all the necessary files. + cmd = 'tar -cvzf /home/ubuntu/kubectl_package.tar.gz ca.crt client.crt client.key config kubectl' # noqa + check_call(split(cmd)) + set_state('kubectl.package.created') + + +@when('proxy.available') +@when_not('cadvisor.available') +def start_cadvisor(): + '''Start the cAdvisor container that gives metrics about the other + application containers on this system. ''' + compose = Compose('files/kubernetes') + compose.up('cadvisor') + set_state('cadvisor.available') + status_set('active', 'cadvisor running on port 8088') + hookenv.open_port(8088) + + +@when('sdn.available') +def gather_sdn_data(): + '''Get the Software Defined Network (SDN) information and return it as a + dictionary.''' + # SDN Providers pass data via the unitdata.kv module + db = unitdata.kv() + # Generate an IP address for the DNS provider + subnet = db.get('sdn_subnet') + if subnet: + ip = subnet.split('/')[0] + dns_server = '.'.join(ip.split('.')[0:-1]) + '.10' + addedcontext = {} + addedcontext['dns_server'] = dns_server + return addedcontext + return {} + + +def copy_key(directory, prefix): + '''Copy the key from the easy-rsa/easyrsa3/pki/private directory to the + specified directory. ''' + if not os.path.isdir(directory): + os.makedirs(directory) + os.chmod(directory, 0o770) + # Must remove the path characters from the local unit name. + path_name = hookenv.local_unit().replace('/', '_') + # The key is not in unitdata it is in the local easy-rsa directory. + local_key_path = 'easy-rsa/easyrsa3/pki/private/{0}.key'.format(path_name) + key_name = '{0}.key'.format(prefix) + # The key should be copied to this directory. + destination_key_path = os.path.join(directory, key_name) + # Copy the key file from the local directory to the destination. + copy2(local_key_path, destination_key_path) + + +def render_files(reldata=None): + '''Use jinja templating to render the docker-compose.yml and master.json + file to contain the dynamic data for the configuration files.''' + context = {} + # Load the context manager with sdn and config data. + context.update(gather_sdn_data()) + context.update(hookenv.config()) + if reldata: + context.update({'connection_string': reldata.connection_string()}) + charm_dir = hookenv.charm_dir() + rendered_kube_dir = os.path.join(charm_dir, 'files/kubernetes') + if not os.path.exists(rendered_kube_dir): + os.makedirs(rendered_kube_dir) + rendered_manifest_dir = os.path.join(charm_dir, 'files/manifests') + if not os.path.exists(rendered_manifest_dir): + os.makedirs(rendered_manifest_dir) + # Add the manifest directory so the docker-compose file can have. + context.update({'manifest_directory': rendered_manifest_dir, + 'private_address': hookenv.unit_get('private-address')}) + + # Render the files/kubernetes/docker-compose.yml file that contains the + # definition for kubelet and proxy. + target = os.path.join(rendered_kube_dir, 'docker-compose.yml') + render('docker-compose.yml', target, context) + # Render the files/manifests/master.json that contains parameters for the + # apiserver, controller, and controller-manager + target = os.path.join(rendered_manifest_dir, 'master.json') + render('master.json', target, context) + # Render files/kubernetes/skydns-svc.yaml for SkyDNS service + target = os.path.join(rendered_manifest_dir, 'skydns-svc.yml') + render('skydns-svc.yml', target, context) + # Render files/kubernetes/skydns-rc.yaml for SkyDNS pods + target = os.path.join(rendered_manifest_dir, 'skydns-rc.yml') + render('skydns-rc.yml', target, context) + + +def save_certificate(directory, prefix): + '''Get the certificate from the charm unitdata, and write it to the proper + directory. The parameters are: destination directory, and prefix to use + for the key and certificate name.''' + if not os.path.isdir(directory): + os.makedirs(directory) + os.chmod(directory, 0o770) + # Grab the unitdata key value store. + store = unitdata.kv() + certificate_data = store.get('tls.{0}.certificate'.format(prefix)) + certificate_name = '{0}.crt'.format(prefix) + # The certificate should be saved to this directory. + certificate_path = os.path.join(directory, certificate_name) + # write the server certificate out to the correct location + with open(certificate_path, 'w') as fp: + fp.write(certificate_data) diff --git a/cluster/juju/layers/kubernetes/templates/docker-compose.yml b/cluster/juju/layers/kubernetes/templates/docker-compose.yml new file mode 100644 index 00000000000..1beb7c4f6a7 --- /dev/null +++ b/cluster/juju/layers/kubernetes/templates/docker-compose.yml @@ -0,0 +1,78 @@ +# https://github.com/kubernetes/kubernetes/blob/master/docs/getting-started-guides/docker.md + +# docker run \ +# --volume=/:/rootfs:ro \ +# --volume=/sys:/sys:ro \ +# --volume=/dev:/dev \ +# --volume=/var/lib/docker/:/var/lib/docker:rw \ +# --volume=/var/lib/kubelet/:/var/lib/kubelet:rw \ +# --volume=/var/run:/var/run:rw \ +# --volume=/var/lib/juju/agents/unit-k8s-0/charm/files/manifests:/etc/kubernetes/manifests:rw \ +# --volume=/srv/kubernetes:/srv/kubernetes \ +# --net=host \ +# --pid=host \ +# --privileged=true \ +# -ti \ +# gcr.io/google_containers/hyperkube:v1.0.6 \ +# /hyperkube kubelet --containerized --hostname-override="127.0.0.1" \ +# --address="0.0.0.0" --api-servers=http://localhost:8080 \ +# --config=/etc/kubernetes/manifests + +kubelet: + image: gcr.io/google_containers/hyperkube:{{version}} + net: host + pid: host + privileged: true + restart: always + volumes: + - /:/rootfs:ro + - /sys:/sys:ro + - /dev:/dev + - /var/lib/docker/:/var/lib/docker:rw + - /var/lib/kubelet/:/var/lib/kubelet:rw + - /var/run:/var/run:rw + - {{manifest_directory}}:/etc/kubernetes/manifests:rw + - /srv/kubernetes:/srv/kubernetes + command: | + /hyperkube kubelet --containerized --hostname-override="{{private_address}}" + --address="0.0.0.0" --api-servers=http://localhost:8080 + --config=/etc/kubernetes/manifests {% if dns_server %} + --cluster-dns={{dns_server}} --cluster-domain=cluster.local {% endif %} + --tls-cert-file="/srv/kubernetes/server.crt" + --tls-private-key-file="/srv/kubernetes/server.key" + +# docker run --net=host -d gcr.io/google_containers/etcd:2.0.12 \ +# /usr/local/bin/etcd --addr=127.0.0.1:4001 --bind-addr=0.0.0.0:4001 \ +# --data-dir=/var/etcd/data +etcd: + net: host + image: gcr.io/google_containers/etcd:2.0.12 + command: | + /usr/local/bin/etcd --addr=127.0.0.1:4001 --bind-addr=0.0.0.0:4001 + --data-dir=/var/etcd/data + +# docker run \ +# -d \ +# --net=host \ +# --privileged \ +# gcr.io/google_containers/hyperkube:v${K8S_VERSION} \ +# /hyperkube proxy --master=http://127.0.0.1:8080 --v=2 +proxy: + net: host + privileged: true + restart: always + image: gcr.io/google_containers/hyperkube:{{version}} + command: /hyperkube proxy --master=http://127.0.0.1:8080 --v=2 + +# cAdvisor (Container Advisor) provides container users an understanding of +# the resource usage and performance characteristics of their running containers. +cadvisor: + image: google/cadvisor:latest + volumes: + - /:/rootfs:ro + - /var/run:/var/run:rw + - /sys:/sys:ro + - /var/lib/docker:/var/lib/docker:ro + ports: + - 8088:8080 + restart: always diff --git a/cluster/juju/layers/kubernetes/templates/master.json b/cluster/juju/layers/kubernetes/templates/master.json new file mode 100644 index 00000000000..52f010911e4 --- /dev/null +++ b/cluster/juju/layers/kubernetes/templates/master.json @@ -0,0 +1,61 @@ +{ +"apiVersion": "v1", +"kind": "Pod", +"metadata": {"name":"k8s-master"}, +"spec":{ + "hostNetwork": true, + "containers":[ + { + "name": "controller-manager", + "image": "gcr.io/google_containers/hyperkube:{{version}}", + "command": [ + "/hyperkube", + "controller-manager", + "--master=127.0.0.1:8080", + "--v=2" + ] + }, + { + "name": "apiserver", + "image": "gcr.io/google_containers/hyperkube:{{version}}", + "command": [ + "/hyperkube", + "apiserver", + "--address=0.0.0.0", + "--client_ca_file=/srv/kubernetes/ca.crt", + "--cluster-name=kubernetes", + "--etcd-servers={{connection_string}}", + "--service-cluster-ip-range={{cidr}}", + "--tls-cert-file=/srv/kubernetes/server.crt", + "--tls-private-key-file=/srv/kubernetes/server.key", + "--v=2" + ], + "volumeMounts": [ + { + "mountPath": "/srv/kubernetes", + "name": "certs-kubernetes", + "readOnly": true + } + ] + }, + { + "name": "scheduler", + "image": "gcr.io/google_containers/hyperkube:{{version}}", + "command": [ + "/hyperkube", + "scheduler", + "--master=127.0.0.1:8080", + "--v=2" + ] + } + ], + "volumes": [ + { + "hostPath": { + "path": "/srv/kubernetes" + }, + "name": "certs-kubernetes" + } + ] + } +} diff --git a/cluster/juju/layers/kubernetes/templates/skydns-rc.yml b/cluster/juju/layers/kubernetes/templates/skydns-rc.yml new file mode 100644 index 00000000000..b3421222b51 --- /dev/null +++ b/cluster/juju/layers/kubernetes/templates/skydns-rc.yml @@ -0,0 +1,92 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: kube-dns-v8 + namespace: kube-system + labels: + k8s-app: kube-dns + version: v8 + kubernetes.io/cluster-service: "true" +spec: + {% if dns_replicas -%} replicas: {{ dns_replicas }} {% else %} replicas: 1 {% endif %} + selector: + k8s-app: kube-dns + version: v8 + template: + metadata: + labels: + k8s-app: kube-dns + version: v8 + kubernetes.io/cluster-service: "true" + spec: + containers: + - name: etcd + image: gcr.io/google_containers/etcd:2.0.9 + resources: + limits: + cpu: 100m + memory: 50Mi + command: + - /usr/local/bin/etcd + - -data-dir + - /var/etcd/data + - -listen-client-urls + - http://127.0.0.1:2379,http://127.0.0.1:4001 + - -advertise-client-urls + - http://127.0.0.1:2379,http://127.0.0.1:4001 + - -initial-cluster-token + - skydns-etcd + volumeMounts: + - name: etcd-storage + mountPath: /var/etcd/data + - name: kube2sky + image: gcr.io/google_containers/kube2sky:1.11 + resources: + limits: + cpu: 100m + memory: 50Mi + args: + # command = "/kube2sky" + {% if dns_domain -%}- -domain={{ dns_domain }} {% else %} - -domain=cluster.local {% endif %} + - -kube_master_url=http://{{ private_address }}:8080 + - name: skydns + image: gcr.io/google_containers/skydns:2015-03-11-001 + resources: + limits: + cpu: 100m + memory: 50Mi + args: + # command = "/skydns" + - -machines=http://localhost:4001 + - -addr=0.0.0.0:53 + {% if dns_domain -%}- -domain={{ dns_domain }}. {% else %} - -domain=cluster.local. {% endif %} + ports: + - containerPort: 53 + name: dns + protocol: UDP + - containerPort: 53 + name: dns-tcp + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + initialDelaySeconds: 30 + timeoutSeconds: 5 + - name: healthz + image: gcr.io/google_containers/exechealthz:1.0 + resources: + limits: + cpu: 10m + memory: 20Mi + args: + {% if dns_domain -%}- -cmd=nslookup kubernetes.default.svc.{{ pillar['dns_domain'] }} localhost >/dev/null {% else %} - -cmd=nslookup kubernetes.default.svc.kubernetes.local localhost >/dev/null {% endif %} + - -port=8080 + ports: + - containerPort: 8080 + protocol: TCP + volumes: + - name: etcd-storage + emptyDir: {} + dnsPolicy: Default # Don't use cluster DNS. diff --git a/cluster/juju/layers/kubernetes/templates/skydns-svc.yml b/cluster/juju/layers/kubernetes/templates/skydns-svc.yml new file mode 100644 index 00000000000..ec0689ada18 --- /dev/null +++ b/cluster/juju/layers/kubernetes/templates/skydns-svc.yml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: kube-dns + namespace: kube-system + labels: + k8s-app: kube-dns + kubernetes.io/cluster-service: "true" + kubernetes.io/name: "KubeDNS" +spec: + selector: + k8s-app: kube-dns + clusterIP: {{ dns_server }} + ports: + - name: dns + port: 53 + protocol: UDP + - name: dns-tcp + port: 53 + protocol: TCP diff --git a/cluster/juju/prereqs/ubuntu-juju.sh b/cluster/juju/prereqs/ubuntu-juju.sh index c85245942b4..a0921eb3917 100644 --- a/cluster/juju/prereqs/ubuntu-juju.sh +++ b/cluster/juju/prereqs/ubuntu-juju.sh @@ -43,6 +43,6 @@ function gather_installation_reqs() { sudo apt-get update fi - package_status 'juju-quickstart' - package_status 'juju-deployer' + package_status 'juju' + package_status 'charm-tools' } diff --git a/cluster/juju/util.sh b/cluster/juju/util.sh index f8113437dff..fed49f0d062 100755 --- a/cluster/juju/util.sh +++ b/cluster/juju/util.sh @@ -25,38 +25,28 @@ JUJU_PATH=$(dirname ${UTIL_SCRIPT}) KUBE_ROOT=$(readlink -m ${JUJU_PATH}/../../) # Use the config file specified in $KUBE_CONFIG_FILE, or config-default.sh. source "${JUJU_PATH}/${KUBE_CONFIG_FILE-config-default.sh}" +# This attempts installation of Juju - This really needs to support multiple +# providers/distros - but I'm super familiar with ubuntu so assume that for now. source ${JUJU_PATH}/prereqs/ubuntu-juju.sh export JUJU_REPOSITORY=${JUJU_PATH}/charms -#KUBE_BUNDLE_URL='https://raw.githubusercontent.com/whitmo/bundle-kubernetes/master/bundles.yaml' KUBE_BUNDLE_PATH=${JUJU_PATH}/bundles/local.yaml -# Build the binaries on the local system and copy the binaries to the Juju charm. + function build-local() { - local targets=( - cmd/kube-proxy \ - cmd/kube-apiserver \ - cmd/kube-controller-manager \ - cmd/kubelet \ - plugin/cmd/kube-scheduler \ - cmd/kubectl \ - test/e2e/e2e.test \ - ) - # Make a clean environment to avoid compiler errors. - make clean - # Build the binaries locally that are used in the charms. - make all WHAT="${targets[*]}" - local OUTPUT_DIR=_output/local/bin/linux/amd64 - mkdir -p cluster/juju/charms/trusty/kubernetes-master/files/output - # Copy the binaries from the output directory to the charm directory. - cp -v $OUTPUT_DIR/* cluster/juju/charms/trusty/kubernetes-master/files/output + # This used to build the kubernetes project. Now it rebuilds the charm(s) + # living in `cluster/juju/layers` + + charm build -o $JUJU_REPOSITORY -s trusty ${JUJU_PATH}/layers/kubernetes } function detect-master() { local kubestatus + # Capturing a newline, and my awk-fu was weak - pipe through tr -d - kubestatus=$(juju status --format=oneline kubernetes-master | grep kubernetes-master/0 | awk '{print $3}' | tr -d "\n") + kubestatus=$(juju status --format=oneline kubernetes | grep ${KUBE_MASTER_NAME} | awk '{print $3}' | tr -d "\n") export KUBE_MASTER_IP=${kubestatus} - export KUBE_SERVER=http://${KUBE_MASTER_IP}:8080 + export KUBE_SERVER=https://${KUBE_MASTER_IP}:6433 + } function detect-nodes() { @@ -74,25 +64,14 @@ function detect-nodes() { export NUM_NODES=${#KUBE_NODE_IP_ADDRESSES[@]} } -function get-password() { - export KUBE_USER=admin - # Get the password from the basic-auth.csv file on kubernetes-master. - export KUBE_PASSWORD=$(juju run --unit kubernetes-master/0 "cat /srv/kubernetes/basic-auth.csv" | grep ${KUBE_USER} | cut -d, -f1) -} - function kube-up() { build-local - if [[ -d "~/.juju/current-env" ]]; then - juju quickstart -i --no-browser - else - juju quickstart --no-browser - fi + # The juju-deployer command will deploy the bundle and can be run # multiple times to continue deploying the parts that fail. - juju deployer -c ${KUBE_BUNDLE_PATH} + juju deploy ${KUBE_BUNDLE_PATH} source "${KUBE_ROOT}/cluster/common.sh" - get-password # Sleep due to juju bug http://pad.lv/1432759 sleep-status @@ -100,31 +79,22 @@ function kube-up() { detect-nodes local prefix=$RANDOM - export KUBE_CERT="/tmp/${prefix}-kubecfg.crt" - export KUBE_KEY="/tmp/${prefix}-kubecfg.key" - export CA_CERT="/tmp/${prefix}-kubecfg.ca" - export CONTEXT="juju" - + export KUBECONFIG=/tmp/${prefix}/config # Copy the cert and key to this machine. ( umask 077 - juju scp kubernetes-master/0:/srv/kubernetes/apiserver.crt ${KUBE_CERT} - juju run --unit kubernetes-master/0 'chmod 644 /srv/kubernetes/apiserver.key' - juju scp kubernetes-master/0:/srv/kubernetes/apiserver.key ${KUBE_KEY} - juju run --unit kubernetes-master/0 'chmod 600 /srv/kubernetes/apiserver.key' - cp ${KUBE_CERT} ${CA_CERT} - - create-kubeconfig + mkdir -p /tmp/${prefix} + juju scp ${KUBE_MASTER_NAME}:kubectl_package.tar.gz /tmp/${prefix}/ + ls -al /tmp/${prefix}/ + tar xfz /tmp/${prefix}/kubectl_package.tar.gz -C /tmp/${prefix} ) } function kube-down() { local force="${1-}" - # Remove the binary files from the charm directory. - rm -rf cluster/juju/charms/trusty/kubernetes-master/files/output/ local jujuenv - jujuenv=$(cat ~/.juju/current-environment) - juju destroy-environment ${jujuenv} ${force} || true + jujuenv=$(juju switch) + juju destroy-model ${jujuenv} ${force} || true } function prepare-e2e() { @@ -140,23 +110,13 @@ function sleep-status() { jujustatus='' echo "Waiting up to 15 minutes to allow the cluster to come online... wait for it..." 1>&2 - jujustatus=$(juju status kubernetes-master --format=oneline) - if [[ $jujustatus == *"started"* ]]; - then - return - fi - - while [[ $i < $maxtime && $jujustatus != *"started"* ]]; do - sleep 15 - i+=15 - jujustatus=$(juju status kubernetes-master --format=oneline) + while [[ $i < $maxtime && -z $jujustatus ]]; do + sleep 15 + i+=15 + jujustatus=$(${JUJU_PATH}/identify-leaders.py) + export KUBE_MASTER_NAME=${jujustatus} done - # sleep because we cannot get the status back of where the minions are in the deploy phase - # thanks to a generic "started" state and our service not actually coming online until the - # minions have received the binary from the master distribution hub during relations - echo "Sleeping an additional minute to allow the cluster to settle" 1>&2 - sleep 60 } # Execute prior to running tests to build a release if required for environment. diff --git a/cluster/kubectl.sh b/cluster/kubectl.sh index 77ad5a13515..30a57760570 100755 --- a/cluster/kubectl.sh +++ b/cluster/kubectl.sh @@ -116,7 +116,7 @@ kubectl="${KUBECTL_PATH:-${kubectl}}" if [[ "$KUBERNETES_PROVIDER" == "gke" ]]; then detect-project &> /dev/null -elif [[ "$KUBERNETES_PROVIDER" == "ubuntu" || "$KUBERNETES_PROVIDER" == "juju" ]]; then +elif [[ "$KUBERNETES_PROVIDER" == "ubuntu" ]]; then detect-master > /dev/null config=( "--server=http://${KUBE_MASTER_IP}:8080" diff --git a/hack/verify-flags/exceptions.txt b/hack/verify-flags/exceptions.txt index 3b00ba87b14..19a2560503e 100644 --- a/hack/verify-flags/exceptions.txt +++ b/hack/verify-flags/exceptions.txt @@ -15,41 +15,11 @@ cluster/gce/configure-vm.sh: kubelet_api_servers: '${KUBELET_APISERVER}' cluster/gce/coreos/helper.sh:# cloud_config yaml file should be passed cluster/gce/trusty/configure.sh: sed -i -e "s@{{pillar\['allow_privileged'\]}}@true@g" "${src_file}" cluster/gce/util.sh: local node_ip=$(gcloud compute instances describe --project "${PROJECT}" --zone "${ZONE}" \ -cluster/juju/charms/trusty/kubernetes-master/files/controller-manager.upstart.tmpl: --address=%(bind_address)s \ -cluster/juju/charms/trusty/kubernetes-master/files/scheduler.upstart.tmpl: --address=%(bind_address)s \ -cluster/juju/charms/trusty/kubernetes-master/hooks/config-changed: for k in ('etcd_servers',): -cluster/juju/charms/trusty/kubernetes-master/hooks/etcd-relation-changed: for k in ('etcd_servers',): -cluster/juju/charms/trusty/kubernetes-master/hooks/hooks.py: for k in ('etcd_servers',): -cluster/juju/charms/trusty/kubernetes-master/hooks/minions-api-relation-changed: for k in ('etcd_servers',): -cluster/juju/charms/trusty/kubernetes-master/hooks/network-relation-changed: for k in ('etcd_servers',): -cluster/juju/charms/trusty/kubernetes/hooks/api-relation-changed: 'http://%s:%s' % (s[0], s[1]) for s in sorted(etcd_servers)]) -cluster/juju/charms/trusty/kubernetes/hooks/api-relation-changed: api_info = api_servers.pop() -cluster/juju/charms/trusty/kubernetes/hooks/api-relation-changed: api_servers = 'http://%s:%s' % (api_info[0], api_info[1]) -cluster/juju/charms/trusty/kubernetes/hooks/api-relation-changed: api_servers = get_rel_hosts('api', rels, ('hostname', 'port')) -cluster/juju/charms/trusty/kubernetes/hooks/api-relation-changed: etcd_servers = get_rel_hosts('etcd', rels, ('hostname', 'port')) -cluster/juju/charms/trusty/kubernetes/hooks/api-relation-changed: for k in ('etcd_servers', 'kubeapi_server'): -cluster/juju/charms/trusty/kubernetes/hooks/api-relation-changed: if api_servers: -cluster/juju/charms/trusty/kubernetes/hooks/etcd-relation-changed: 'http://%s:%s' % (s[0], s[1]) for s in sorted(etcd_servers)]) -cluster/juju/charms/trusty/kubernetes/hooks/etcd-relation-changed: api_info = api_servers.pop() -cluster/juju/charms/trusty/kubernetes/hooks/etcd-relation-changed: api_servers = 'http://%s:%s' % (api_info[0], api_info[1]) -cluster/juju/charms/trusty/kubernetes/hooks/etcd-relation-changed: api_servers = get_rel_hosts('api', rels, ('hostname', 'port')) -cluster/juju/charms/trusty/kubernetes/hooks/etcd-relation-changed: etcd_servers = get_rel_hosts('etcd', rels, ('hostname', 'port')) -cluster/juju/charms/trusty/kubernetes/hooks/etcd-relation-changed: for k in ('etcd_servers', 'kubeapi_server'): -cluster/juju/charms/trusty/kubernetes/hooks/etcd-relation-changed: if api_servers: -cluster/juju/charms/trusty/kubernetes/hooks/hooks.py: 'http://%s:%s' % (s[0], s[1]) for s in sorted(etcd_servers)]) -cluster/juju/charms/trusty/kubernetes/hooks/hooks.py: api_info = api_servers.pop() -cluster/juju/charms/trusty/kubernetes/hooks/hooks.py: api_servers = 'http://%s:%s' % (api_info[0], api_info[1]) -cluster/juju/charms/trusty/kubernetes/hooks/hooks.py: api_servers = get_rel_hosts('api', rels, ('hostname', 'port')) -cluster/juju/charms/trusty/kubernetes/hooks/hooks.py: etcd_servers = get_rel_hosts('etcd', rels, ('hostname', 'port')) -cluster/juju/charms/trusty/kubernetes/hooks/hooks.py: for k in ('etcd_servers', 'kubeapi_server'): -cluster/juju/charms/trusty/kubernetes/hooks/hooks.py: if api_servers: -cluster/juju/charms/trusty/kubernetes/hooks/network-relation-changed: 'http://%s:%s' % (s[0], s[1]) for s in sorted(etcd_servers)]) -cluster/juju/charms/trusty/kubernetes/hooks/network-relation-changed: api_info = api_servers.pop() -cluster/juju/charms/trusty/kubernetes/hooks/network-relation-changed: api_servers = 'http://%s:%s' % (api_info[0], api_info[1]) -cluster/juju/charms/trusty/kubernetes/hooks/network-relation-changed: api_servers = get_rel_hosts('api', rels, ('hostname', 'port')) -cluster/juju/charms/trusty/kubernetes/hooks/network-relation-changed: etcd_servers = get_rel_hosts('etcd', rels, ('hostname', 'port')) -cluster/juju/charms/trusty/kubernetes/hooks/network-relation-changed: for k in ('etcd_servers', 'kubeapi_server'): -cluster/juju/charms/trusty/kubernetes/hooks/network-relation-changed: if api_servers: +cluster/juju/layers/kubernetes/reactive/k8s.py: check_call(split(cmd.format(directory, cluster_name, public_address, +cluster/juju/layers/kubernetes/reactive/k8s.py: check_call(split(cmd.format(directory, context, cluster_name, user))) +cluster/juju/layers/kubernetes/reactive/k8s.py: cluster_name = 'kubernetes' +cluster/juju/layers/kubernetes/templates/master.json: "--client_ca_file=/srv/kubernetes/ca.crt", +cluster/juju/layers/kubernetes/templates/skydns-rc.yml: - -kube_master_url=http://{{ private_address }}:8080 cluster/lib/logging.sh: local source_file=${BASH_SOURCE[$frame_no]} cluster/lib/logging.sh: local source_file=${BASH_SOURCE[$stack_skip]} cluster/log-dump.sh: for node_name in "${NODE_NAMES[@]}"; do