Merge pull request #22726 from chuckbutler/juju-import-layers

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot 2016-03-23 14:26:42 -07:00
commit 69b3cb36a6
70 changed files with 801 additions and 2513 deletions

3
.gitignore vendored
View File

@ -88,6 +88,9 @@ doc_tmp/
# CoreOS stuff # CoreOS stuff
cluster/libvirt-coreos/coreos_*.img cluster/libvirt-coreos/coreos_*.img
# Juju Stuff
cluster/juju/charms/*
# Downloaded Kubernetes binary release # Downloaded Kubernetes binary release
kubernetes/ kubernetes/

View File

@ -1,53 +1,18 @@
kubernetes-local: services:
services: kubernetes:
kubernetes-master: charm: local:trusty/kubernetes
charm: local:trusty/kubernetes-master annotations:
annotations: "gui-x": "600"
"gui-x": "600" "gui-y": "0"
"gui-y": "0" expose: true
expose: true num_units: 2
options: etcd:
version: "local" charm: cs:~containers/trusty/etcd
docker: annotations:
charm: cs:trusty/docker "gui-x": "300"
num_units: 2 "gui-y": "0"
options: num_units: 1
latest: true relations:
annotations: - - "kubernetes:etcd"
"gui-x": "0" - "etcd:db"
"gui-y": "0" series: trusty
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

View File

@ -1 +0,0 @@
/docker

View File

@ -1,5 +0,0 @@
*~
.bzr
.venv
unit_tests/__pycache__
*.pyc

View File

@ -1,5 +0,0 @@
omit:
- .git
- .gitignore
- .gitmodules
- revision

View File

@ -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

View File

@ -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)]()

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -1 +0,0 @@
install.py

View File

@ -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()

View File

@ -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)

View File

@ -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'])

View File

@ -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 <matthew.bruzek@canonical.com>
- Whit Morriss <whit.morriss@canonical.com>
- Charles Butler <charles.butler@canonical.com>
tags:
- ops
- network
provides:
client-api:
interface: kubernetes-client
minions-api:
interface: kubernetes-api
requires:
etcd:
interface: etcd
network:
interface: overlay-network

View File

@ -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

View File

@ -1,5 +0,0 @@
flake8
pytest
bundletester
path.py
charmhelpers

View File

@ -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()

View File

@ -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)

View File

@ -1 +0,0 @@
.git

View File

@ -1,6 +0,0 @@
.bzr
*.pyc
*~
*\#*
/files/.kubernetes-*
.venv

View File

@ -1,5 +0,0 @@
omit:
- .git
- .gitignore
- .gitmodules
- revision

View File

@ -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

View File

@ -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)]()

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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")

View File

@ -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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 76 KiB

View File

@ -1,23 +0,0 @@
name: kubernetes
summary: Container Cluster Management Node
maintainers:
- Matt Bruzek <matthew.bruzek@canonical.com>
- Whit Morriss <whit.morriss@canonical.com>
- Charles Butler <charles.butler@canonical.com>
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

View File

@ -1,4 +0,0 @@
flake8
pytest
bundletester
path.py

View File

@ -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)

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# import pytest from subprocess import check_output
import yaml
out = check_output(['juju', 'status', 'kubernetes', '--format=yaml'])
class TestHooks(): try:
parsed_output = yaml.safe_load(out)
# TODO: Actually write tests. model = parsed_output['services']['kubernetes']['units']
def test_fake(self): for unit in model:
pass if 'workload-status' in model[unit].keys():
if 'leader' in model[unit]['workload-status']['message']:
print(unit)
except:
pass

View File

@ -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 &lt;Matthew.Bruzek@canonical.com&gt;
* Charm Contributor: Charles Butler &lt;Charles.Butler@canonical.com&gt;
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/cluster/juju/layers/kubernetes/README.md?pixel)]()

View File

@ -0,0 +1,2 @@
guestbook-example:
description: Launch the guestbook example in your k8s cluster

View File

@ -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

View File

@ -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

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -0,0 +1 @@
includes: ['layer:docker', 'layer:flannel', 'layer:tls', 'interface:etcd']

View File

@ -0,0 +1,17 @@
name: kubernetes
summary: Kubernetes is an application container orchestration platform.
maintainers:
- Matthew Bruzek <matthew.bruzek@canonical.com>
- Charles Butler <charles.butler@canonical.com>
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

View File

@ -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)

View File

@ -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

View File

@ -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"
}
]
}
}

View File

@ -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.

View File

@ -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

View File

@ -43,6 +43,6 @@ function gather_installation_reqs() {
sudo apt-get update sudo apt-get update
fi fi
package_status 'juju-quickstart' package_status 'juju'
package_status 'juju-deployer' package_status 'charm-tools'
} }

View File

@ -25,38 +25,28 @@ JUJU_PATH=$(dirname ${UTIL_SCRIPT})
KUBE_ROOT=$(readlink -m ${JUJU_PATH}/../../) KUBE_ROOT=$(readlink -m ${JUJU_PATH}/../../)
# Use the config file specified in $KUBE_CONFIG_FILE, or config-default.sh. # Use the config file specified in $KUBE_CONFIG_FILE, or config-default.sh.
source "${JUJU_PATH}/${KUBE_CONFIG_FILE-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 source ${JUJU_PATH}/prereqs/ubuntu-juju.sh
export JUJU_REPOSITORY=${JUJU_PATH}/charms 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 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() { function build-local() {
local targets=( # This used to build the kubernetes project. Now it rebuilds the charm(s)
cmd/kube-proxy \ # living in `cluster/juju/layers`
cmd/kube-apiserver \
cmd/kube-controller-manager \ charm build -o $JUJU_REPOSITORY -s trusty ${JUJU_PATH}/layers/kubernetes
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
} }
function detect-master() { function detect-master() {
local kubestatus local kubestatus
# Capturing a newline, and my awk-fu was weak - pipe through tr -d # 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_MASTER_IP=${kubestatus}
export KUBE_SERVER=http://${KUBE_MASTER_IP}:8080 export KUBE_SERVER=https://${KUBE_MASTER_IP}:6433
} }
function detect-nodes() { function detect-nodes() {
@ -74,25 +64,14 @@ function detect-nodes() {
export NUM_NODES=${#KUBE_NODE_IP_ADDRESSES[@]} 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() { function kube-up() {
build-local 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 # The juju-deployer command will deploy the bundle and can be run
# multiple times to continue deploying the parts that fail. # 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" source "${KUBE_ROOT}/cluster/common.sh"
get-password
# Sleep due to juju bug http://pad.lv/1432759 # Sleep due to juju bug http://pad.lv/1432759
sleep-status sleep-status
@ -100,31 +79,22 @@ function kube-up() {
detect-nodes detect-nodes
local prefix=$RANDOM local prefix=$RANDOM
export KUBE_CERT="/tmp/${prefix}-kubecfg.crt" export KUBECONFIG=/tmp/${prefix}/config
export KUBE_KEY="/tmp/${prefix}-kubecfg.key"
export CA_CERT="/tmp/${prefix}-kubecfg.ca"
export CONTEXT="juju"
# Copy the cert and key to this machine. # Copy the cert and key to this machine.
( (
umask 077 umask 077
juju scp kubernetes-master/0:/srv/kubernetes/apiserver.crt ${KUBE_CERT} mkdir -p /tmp/${prefix}
juju run --unit kubernetes-master/0 'chmod 644 /srv/kubernetes/apiserver.key' juju scp ${KUBE_MASTER_NAME}:kubectl_package.tar.gz /tmp/${prefix}/
juju scp kubernetes-master/0:/srv/kubernetes/apiserver.key ${KUBE_KEY} ls -al /tmp/${prefix}/
juju run --unit kubernetes-master/0 'chmod 600 /srv/kubernetes/apiserver.key' tar xfz /tmp/${prefix}/kubectl_package.tar.gz -C /tmp/${prefix}
cp ${KUBE_CERT} ${CA_CERT}
create-kubeconfig
) )
} }
function kube-down() { function kube-down() {
local force="${1-}" local force="${1-}"
# Remove the binary files from the charm directory.
rm -rf cluster/juju/charms/trusty/kubernetes-master/files/output/
local jujuenv local jujuenv
jujuenv=$(cat ~/.juju/current-environment) jujuenv=$(juju switch)
juju destroy-environment ${jujuenv} ${force} || true juju destroy-model ${jujuenv} ${force} || true
} }
function prepare-e2e() { function prepare-e2e() {
@ -140,23 +110,13 @@ function sleep-status() {
jujustatus='' jujustatus=''
echo "Waiting up to 15 minutes to allow the cluster to come online... wait for it..." 1>&2 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) while [[ $i < $maxtime && -z $jujustatus ]]; do
if [[ $jujustatus == *"started"* ]]; sleep 15
then i+=15
return jujustatus=$(${JUJU_PATH}/identify-leaders.py)
fi export KUBE_MASTER_NAME=${jujustatus}
while [[ $i < $maxtime && $jujustatus != *"started"* ]]; do
sleep 15
i+=15
jujustatus=$(juju status kubernetes-master --format=oneline)
done 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. # Execute prior to running tests to build a release if required for environment.

View File

@ -116,7 +116,7 @@ kubectl="${KUBECTL_PATH:-${kubectl}}"
if [[ "$KUBERNETES_PROVIDER" == "gke" ]]; then if [[ "$KUBERNETES_PROVIDER" == "gke" ]]; then
detect-project &> /dev/null detect-project &> /dev/null
elif [[ "$KUBERNETES_PROVIDER" == "ubuntu" || "$KUBERNETES_PROVIDER" == "juju" ]]; then elif [[ "$KUBERNETES_PROVIDER" == "ubuntu" ]]; then
detect-master > /dev/null detect-master > /dev/null
config=( config=(
"--server=http://${KUBE_MASTER_IP}:8080" "--server=http://${KUBE_MASTER_IP}:8080"

View File

@ -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/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/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/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/layers/kubernetes/reactive/k8s.py: check_call(split(cmd.format(directory, cluster_name, public_address,
cluster/juju/charms/trusty/kubernetes-master/files/scheduler.upstart.tmpl: --address=%(bind_address)s \ cluster/juju/layers/kubernetes/reactive/k8s.py: check_call(split(cmd.format(directory, context, cluster_name, user)))
cluster/juju/charms/trusty/kubernetes-master/hooks/config-changed: for k in ('etcd_servers',): cluster/juju/layers/kubernetes/reactive/k8s.py: cluster_name = 'kubernetes'
cluster/juju/charms/trusty/kubernetes-master/hooks/etcd-relation-changed: for k in ('etcd_servers',): cluster/juju/layers/kubernetes/templates/master.json: "--client_ca_file=/srv/kubernetes/ca.crt",
cluster/juju/charms/trusty/kubernetes-master/hooks/hooks.py: for k in ('etcd_servers',): cluster/juju/layers/kubernetes/templates/skydns-rc.yml: - -kube_master_url=http://{{ private_address }}:8080
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/lib/logging.sh: local source_file=${BASH_SOURCE[$frame_no]} cluster/lib/logging.sh: local source_file=${BASH_SOURCE[$frame_no]}
cluster/lib/logging.sh: local source_file=${BASH_SOURCE[$stack_skip]} cluster/lib/logging.sh: local source_file=${BASH_SOURCE[$stack_skip]}
cluster/log-dump.sh: for node_name in "${NODE_NAMES[@]}"; do cluster/log-dump.sh: for node_name in "${NODE_NAMES[@]}"; do