Changes necessary to enable ssl for the Juju charms

Squashed commit of the following:

commit dedaccffdc1d797b5f23e0004280c2fcc0ecffa9
Merge: 24c3585 f03a267
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Tue Sep 15 17:07:42 2015 -0500

    Merge branch 'master' of github.com:kubernetes/kubernetes into enable-ssl

commit 24c358566cc0963fb86dc057db15739f031ba6f6
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Tue Sep 15 16:44:58 2015 -0500

    Fixing problems with verify-boilerplate.

commit a64443352c64498255ac98fc0da1a7d8d5934485
Merge: f152794 ee3f662
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Tue Sep 15 15:43:08 2015 -0500

    Merge branch 'enable-ssl' of github.com:mbruzek/kubernetes into enable-ssl

    Conflicts:
    	cluster/juju/util.sh

commit f152794502c017ae7d3cff0351d8bf44b7311883
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 14:12:21 2015 -0500

    Fixes for problems found in testing.

commit 94effa4827d5f30c60621e9133c4526c187e40b4
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 12:34:37 2015 -0500

    Making updates for changes in master branch.

commit a81795b44e57d54b8b4ae486ca6ea8164ac8fc6b
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:39:36 2015 -0500

    pep8 fix

commit 53a862caea02c4b35f8cd19b1549fda29e040f12
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:37:31 2015 -0500

    Adding diagnostic log messages and reloading nginx.

commit 96411a924fb05e2e58534cce94d9a1e51d7db9af
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:19:31 2015 -0500

    Making the check user logic cleaner.

commit a0243b34cdda2f865e559bd4812c5a78ab6f6f05
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:18:04 2015 -0500

    Open port 6443 for ssl enablement.

commit e8423614763aa6d650089c735c3dc1893bf73993
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:16:54 2015 -0500

    Generating certificates and adding config options.

commit 6570a818e252f2cb156a577094ba987dec834fe1
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:14:56 2015 -0500

    Removing the http configuration adding https config.

commit e624bd79f8840b71b141a111bca7c6091b677575
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:13:04 2015 -0500

    Changed the distribution nginx config slightly.

commit c497911170268ee75bed53afeb5fa32ff44c82af
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 10:57:17 2015 -0500

    Adding the crt and key to the apiserver flags.

commit 6c1e76c5de31eb4d0f800065ce4bc96a82801846
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 10:56:15 2015 -0500

    Adding the cert and key configuration parameters.

commit 55da910d473efa0be0af5efccf2336612525986e
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 10:49:46 2015 -0500

    Adding a requirements file to install path.py with pip -r

commit 27a39686af89e134268be50ce5e4fc36cffdf2b3
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Fri Aug 21 16:34:56 2015 -0500

    Making the install hook idempotent.

commit ee3f66287ba045592f932c3a41aeb8e0167a9235
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 14:12:21 2015 -0500

    Fixes for problems found in testing.

commit 3dfdbb0e21d79da66617f7a29e6dd8d977528057
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 12:34:37 2015 -0500

    Making updates for changes in master branch.

commit df9c297fe27c63713fc0d7dbd461b2d47133614c
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:39:36 2015 -0500

    pep8 fix

commit 645b25d9cc84555ca7af5c75e3f0b1300aaa9f48
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:37:31 2015 -0500

    Adding diagnostic log messages and reloading nginx.

commit 57654320bd73dc4dd52d6d56021d40a97c6ed893
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:37:02 2015 -0500

    Removing xtrace.

commit a0e8cd98353e7cd411786bc8836fe99a77cde3ba
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:19:31 2015 -0500

    Making the check user logic cleaner.

commit 6c6eb7ff02d6dbf66d3175b715e957b00a861525
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:18:04 2015 -0500

    Open port 6443 for ssl enablement.

commit 29f18cc95ff96de1a48f72af2cff2e37420a43c7
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:16:54 2015 -0500

    Generating certificates and adding config options.

commit c9bdaa499552980153ff263a1ab9b4fa73a0536a
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:14:56 2015 -0500

    Removing the http configuration adding https config.

commit ec33e66a159a4b44207353b16741c7fad2e4ee0d
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 11:13:04 2015 -0500

    Changed the distribution nginx config slightly.

commit 96dc16879c0dd230569ceb928f9f7bf92ff8ab3f
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 10:57:17 2015 -0500

    Adding the crt and key to the apiserver flags.

commit 308799502c0a22f214408395f5efa4821d075374
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 10:56:15 2015 -0500

    Adding the cert and key configuration parameters.

commit 7f407a4356de8703ff2f237432084f35910d8abd
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Wed Sep 9 10:49:46 2015 -0500

    Adding a requirements file to install path.py with pip -r

commit f800ae1152076758d4db203fdbecf6d945c0e892
Author: Matt Bruzek <matthew.bruzek@canonical.com>
Date:   Fri Aug 21 16:34:56 2015 -0500

    Making the install hook idempotent.

Resolving verification problems.
pull/6/head
Matt Bruzek 2015-09-15 17:19:16 -05:00
parent f03a267089
commit 7a387a87a1
19 changed files with 345 additions and 175 deletions

View File

@ -1,9 +1,33 @@
options:
version:
type: string
default: "v0.15.0"
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

@ -8,13 +8,12 @@ limit nofile 20000 20000
kill timeout 30 # wait 30s between SIGTERM and SIGKILL.
exec /usr/local/bin/apiserver \
--address=%(api_bind_address)s \
--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 \
--service-cluster-ip-range=10.244.240.0/20
--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

@ -10,11 +10,4 @@ kill timeout 30 # wait 30s between SIGTERM and SIGKILL.
exec /usr/local/bin/controller-manager \
--address=%(bind_address)s \
--logtostderr=true \
--master=%(api_server_address)s
--master=%(api_http_uri)s

View File

@ -1,5 +1,9 @@
# 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 %(api_bind_address)s:80;
listen 80 default_server;
root %(alias)s;
location %(web_uri)s {
alias %(alias)s;
}

View File

@ -1,39 +1,27 @@
# HTTP/HTTPS server
#
# Proxy HTTPS from the public address to the kube-apiserver running at 6443.
server {
listen 80;
server_name localhost;
listen 443;
server_name localhost;
root html;
index index.html index.htm;
root html;
index index.html index.htm;
# ssl on;
# ssl_certificate /usr/share/nginx/server.cert;
# ssl_certificate_key /usr/share/nginx/server.key;
ssl on;
ssl_certificate /srv/kubernetes/apiserver.crt;
ssl_certificate_key /srv/kubernetes/apiserver.key;
ssl_session_timeout 5m;
# ssl_session_timeout 5m;
# ssl_protocols SSLv3 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;
# 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 / {
# auth_basic "Restricted";
# auth_basic_user_file /usr/share/nginx/htpasswd;
# Proxy settings
# disable buffering so that watch works
proxy_buffering off;
proxy_pass %(api_server_address)s;
proxy_connect_timeout 159s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
# Disable retry
proxy_next_upstream off;
# Support web sockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
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

@ -10,11 +10,4 @@ kill timeout 30 # wait 30s between SIGTERM and SIGKILL.
exec /usr/local/bin/scheduler \
--address=%(bind_address)s \
--logtostderr=true \
--master=%(api_server_address)s
--master=%(api_http_uri)s

View File

@ -23,8 +23,9 @@ 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
from path import Path
hooks = hookenv.Hooks()
@ -55,10 +56,14 @@ def config_changed():
create kubernetes binary files.
"""
hookenv.log('Starting config-changed')
charm_dir = path(hookenv.charm_dir())
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.
@ -70,32 +75,59 @@ def config_changed():
# Create a branch to a tag to get the release version.
branch = 'tags/{0}'.format(version)
# Get the package architecture, rather than arch from the kernel (uname -m).
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(config['username'], config['username'], config['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'
installer = KubernetesInstaller(arch, version, output_path)
kube_installer = KubernetesInstaller(arch, version, output_path)
else:
# Build the kuberentes binaries from source on the units.
kubernetes_dir = path('/opt/kubernetes')
kubernetes_dir = Path('/opt/kubernetes')
# Construct the path to the binaries using the arch.
output_path = kubernetes_dir / '_output/local/bin/linux' / arch
installer = KubernetesInstaller(arch, version, output_path)
kube_installer = KubernetesInstaller(arch, version, output_path)
if not kubernetes_dir.exists():
print('The source directory {0} does not exist'.format(kubernetes_dir))
print('Was the kubernetes code cloned during install?')
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).strip()
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'
@ -104,12 +136,12 @@ def config_changed():
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:
installer.build(branch)
kube_installer.build(branch)
if not output_path.isdir():
broken_build.touch()
# Create the symoblic links to the right directories.
installer.install()
kube_installer.install()
relation_changed()
@ -123,10 +155,10 @@ def relation_changed():
# Check required keys
for k in ('etcd_servers',):
if not template_data.get(k):
print "Missing data for", k, template_data
print 'Missing data for', k, template_data
return
print "Running with\n", template_data
print 'Running with\n', template_data
# Render and restart as needed
for n in ('apiserver', 'controller-manager', 'scheduler'):
@ -157,7 +189,7 @@ def network_relation_changed():
def notify_minions():
print("Notify minions.")
print('Notify minions.')
config = hookenv.config()
for r in hookenv.relation_ids('minions-api'):
hookenv.relation_set(
@ -165,7 +197,49 @@ def notify_minions():
hostname=hookenv.unit_private_ip(),
port=8080,
version=config['version'])
print("Notified minions of 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():
@ -173,18 +247,21 @@ def get_template_data():
config = hookenv.config()
version = config['version']
template_data = {}
template_data['etcd_servers'] = ",".join([
"http://%s:%s" % (s[0], s[1]) for s in sorted(
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))
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)
template_data['api_bind_address'] = _bind_addr(hookenv.unit_private_ip())
template_data['bind_address'] = "127.0.0.1"
template_data['api_server_address'] = "http://%s:%s" % (
hookenv.unit_private_ip(), 8080)
arch = subprocess.check_output(['dpkg', '--print-architecture']).strip()
template_data['web_uri'] = "/kubernetes/%s/local/bin/linux/%s/" % (version,
template_data['web_uri'] = '/kubernetes/%s/local/bin/linux/%s/' % (version,
arch)
if version == 'local':
template_data['alias'] = hookenv.charm_dir() + '/files/output/'
@ -201,7 +278,7 @@ def _bind_addr(addr):
try:
return socket.gethostbyname(addr)
except socket.error:
raise ValueError("Could not resolve private address")
raise ValueError('Could not resolve address %s' % addr)
def _encode(d):
@ -223,7 +300,7 @@ def get_rel_hosts(rel_name, rels, keys=('private-address',)):
return hosts
def render_file(name, data, src_suffix="upstart.tmpl", tgt_path=None):
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))
@ -244,5 +321,6 @@ def render_file(name, data, src_suffix="upstart.tmpl", tgt_path=None):
fh.write(rendered)
return True
if __name__ == '__main__':
hooks.execute(sys.argv)

View File

@ -18,10 +18,10 @@ import setup
setup.pre_install()
import subprocess
from charmhelpers.core import hookenv
from charmhelpers import fetch
from charmhelpers.core import hookenv
from charmhelpers.fetch import archiveurl
from path import path
from path import Path
def install():
@ -30,21 +30,28 @@ def install():
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 KUBE_MASTER_IP=0.0.0.0\n',
'export KUBERNETES_MASTER=http://$KUBE_MASTER_IP\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
@ -59,12 +66,12 @@ def download_go():
def clone_repository():
"""
Clone the upstream repository into /opt/kubernetes for deployment compilation
of kubernetes. Subsequently used during upgrades.
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')
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()
@ -75,7 +82,6 @@ def clone_repository():
print(output)
def install_packages():
"""
Install required packages to build the k8s source, and syndicate between
@ -83,17 +89,21 @@ def install_packages():
"""
hookenv.log('Installing Debian packages')
# Create the list of packages to install.
apt_packages = ['build-essential', 'git', 'make', 'nginx', 'python-pip']
apt_packages = ['apache2-utils',
'build-essential',
'git',
'make',
'nginx',
'python-pip', ]
fetch.apt_install(fetch.filter_installed_packages(apt_packages))
def update_rc_files(strings):
"""
Preseed the bash environment for ubuntu and root with K8's env vars to
make interfacing with the api easier. (see: kubectrl docs)
"""
rc_files = [path('/home/ubuntu/.bashrc'), path('/root/.bashrc')]
rc_files = [Path('/home/ubuntu/.bashrc'), Path('/root/.bashrc')]
for rc_file in rc_files:
lines = rc_file.lines()
for string in strings:
@ -102,6 +112,5 @@ def update_rc_files(strings):
rc_file.write_lines(lines)
if __name__ == "__main__":
install()

View File

@ -17,7 +17,7 @@
import os
import shlex
import subprocess
from path import path
from path import Path
def run(command, shell=False):
@ -46,7 +46,7 @@ class KubernetesInstaller():
'kubelet': 'kubelet'}
self.arch = arch
self.version = version
self.output_dir = path(output_dir)
self.output_dir = Path(output_dir)
def build(self, branch):
""" Build kubernetes from a github repository using the Makefile. """
@ -88,7 +88,7 @@ class KubernetesInstaller():
print(make_what)
rc = subprocess.call(shlex.split(make_what), env=go_env)
def install(self, install_dir=path('/usr/local/bin')):
def install(self, install_dir=Path('/usr/local/bin')):
""" Install kubernetes binary files from the output directory. """
if not install_dir.isdir():

View File

@ -14,6 +14,7 @@
# 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.

View File

@ -15,7 +15,6 @@
# limitations under the License.
from mock import patch
from path import path
from path import Path
import pytest
import subprocess
@ -38,7 +37,7 @@ def test_run():
assert output
assert 'kubernetes_installer.py' in output
invalid_directory = path('/not/a/real/directory')
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:
@ -67,13 +66,13 @@ class TestKubernetesInstaller():
assert 'kubelet' in ki.aliases
assert ki.arch == 'i386'
assert ki.version == '3.0.1'
assert ki.output_dir == path('/tmp/does_not_exist')
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')
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.
@ -94,7 +93,7 @@ class TestKubernetesInstaller():
def test_install(self):
""" Test the install method that it creates the correct links. """
directory = path('/tmp/kubernetes_installer_test/install')
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()

View File

@ -17,10 +17,7 @@
"""
The main hook file that is called by Juju.
"""
import json
import httplib
import os
import time
import socket
import subprocess
import sys
@ -28,7 +25,7 @@ import urlparse
from charmhelpers.core import hookenv, host
from kubernetes_installer import KubernetesInstaller
from path import path
from path import Path
from lib.registrator import Registrator
@ -43,10 +40,10 @@ def api_relation_changed():
from the kubernetes-master charm and installs it locally on this machine.
"""
hookenv.log('Starting api-relation-changed')
charm_dir = path(hookenv.charm_dir())
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')
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)
@ -228,6 +225,7 @@ def register_machine(apiserver, retry=False):
# for now this is OK
pass
def setup_kubernetes_group():
output = subprocess.check_output(['groups', 'kubernetes'])

View File

@ -13,20 +13,21 @@ apt-get install -q -y \
python-pip \
wget
pip install path.py
pip install -r $CHARM_DIR/python_requirements.txt
# Create the necessary kubernetes group.
groupadd kubernetes
useradd -d /var/lib/kubernetes \
-g kubernetes \
-s /sbin/nologin \
--system \
kubernetes
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
# wait for the world, depends on where we installed it from distro
#sudo service docker.io stop
# or upstream archive
#sudo service docker stop

View File

@ -15,7 +15,7 @@
# limitations under the License.
import subprocess
from path import path
from path import Path
class KubernetesInstaller():
@ -49,7 +49,7 @@ class KubernetesInstaller():
print(output)
destination.chmod(0o755)
def install(self, install_dir=path('/usr/local/bin')):
def install(self, install_dir=Path('/usr/local/bin')):
""" Create links to the binary files to the install directory. """
if not install_dir.isdir():

View File

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

View File

@ -0,0 +1,16 @@
#!/bin/bash
# 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

@ -0,0 +1,17 @@
#!/bin/bash
# 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.
NUM_MINIONS=${NUM_MINIONS:-2}

View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: kube-system

View File

@ -18,29 +18,68 @@
set -o errexit
set -o nounset
set -o pipefail
#set -o xtrace
UTIL_SCRIPT=$(readlink -m "${BASH_SOURCE}")
JUJU_PATH=$(dirname ${UTIL_SCRIPT})
KUBE_ROOT=$(readlink -m ${JUJU_PATH}/../../)
# Use the config file specified in $KUBE_CONFIG_FILE, or config-default.sh.
source "${JUJU_PATH}/${KUBE_CONFIG_FILE-config-default.sh}"
source ${JUJU_PATH}/prereqs/ubuntu-juju.sh
export JUJU_REPOSITORY=${JUJU_PATH}/charms
#KUBE_BUNDLE_URL='https://raw.githubusercontent.com/whitmo/bundle-kubernetes/master/bundles.yaml'
KUBE_BUNDLE_PATH=${JUJU_PATH}/bundles/local.yaml
function verify-prereqs() {
gather_installation_reqs
}
# Build the binaries on the local system and copy the binaries to the Juju charm.
function build-local() {
local targets=(
cmd/kube-proxy \
cmd/kube-apiserver \
cmd/kube-controller-manager \
cmd/kubelet \
plugin/cmd/kube-scheduler \
cmd/kubectl \
test/e2e/e2e.test \
)
# Make a clean environment to avoid compiler errors.
make clean
# Build the binaries locally that are used in the charms.
make all WHAT="cmd/kube-apiserver cmd/kubectl cmd/kube-controller-manager plugin/cmd/kube-scheduler cmd/kubelet cmd/kube-proxy"
OUTPUT_DIR=_output/local/bin/linux/amd64
make all WHAT="${targets[*]}"
local OUTPUT_DIR=_output/local/bin/linux/amd64
mkdir -p cluster/juju/charms/trusty/kubernetes-master/files/output
# Copy the binary output to the charm directory.
# 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() {
local kubestatus
# Capturing a newline, and my awk-fu was weak - pipe through tr -d
kubestatus=$(juju status --format=oneline kubernetes-master | grep kubernetes-master/0 | awk '{print $3}' | tr -d "\n")
export KUBE_MASTER_IP=${kubestatus}
export KUBE_SERVER=http://${KUBE_MASTER_IP}:8080
}
function detect-minions() {
# Run the Juju command that gets the minion private IP addresses.
local ipoutput
ipoutput=$(juju run --service kubernetes "unit-get private-address" --format=json)
# [
# {"MachineId":"2","Stdout":"192.168.122.188\n","UnitId":"kubernetes/0"},
# {"MachineId":"3","Stdout":"192.168.122.166\n","UnitId":"kubernetes/1"}
# ]
# Strip out the IP addresses
export KUBE_MINION_IP_ADDRESSES=($(${JUJU_PATH}/return-node-ips.py "${ipoutput}"))
# echo "Kubernetes minions: " ${KUBE_MINION_IP_ADDRESSES[@]} 1>&2
export NUM_MINIONS=${#KUBE_MINION_IP_ADDRESSES[@]}
}
function get-password() {
export KUBE_USER=admin
# Get the password from the basic-auth.csv file on kubernetes-master.
export KUBE_PASSWORD=$(juju run --unit kubernetes-master/0 "cat /srv/kubernetes/basic-auth.csv" | grep ${KUBE_USER} | cut -d, -f1)
}
function kube-up() {
build-local
if [[ -d "~/.juju/current-env" ]]; then
@ -51,61 +90,45 @@ function kube-up() {
# The juju-deployer command will deploy the bundle and can be run
# multiple times to continue deploying the parts that fail.
juju deployer -c ${KUBE_BUNDLE_PATH}
source "${KUBE_ROOT}/cluster/common.sh"
get-password
# Sleep due to juju bug http://pad.lv/1432759
sleep-status
detect-master
detect-minions
export KUBE_MASTER_IP="${KUBE_MASTER_IP}:8080"
local prefix=$RANDOM
export KUBE_CERT="/tmp/${prefix}-kubecfg.crt"
export KUBE_KEY="/tmp/${prefix}-kubecfg.key"
export CA_CERT="/tmp/${prefix}-kubecfg.ca"
export CONTEXT="juju"
# Copy the cert and key to this machine.
(
umask 077
juju scp kubernetes-master/0:/srv/kubernetes/apiserver.crt ${KUBE_CERT}
juju run --unit kubernetes-master/0 'chmod 644 /srv/kubernetes/apiserver.key'
juju scp kubernetes-master/0:/srv/kubernetes/apiserver.key ${KUBE_KEY}
juju run --unit kubernetes-master/0 'chmod 600 /srv/kubernetes/apiserver.key'
cp ${KUBE_CERT} ${CA_CERT}
create-kubeconfig
)
}
function kube-down() {
local force="${1-}"
# Remove the binary files from the charm directory.
rm -rf cluster/juju/charms/trusty/kubernetes-master/files/output/
local jujuenv
jujuenv=$(cat ~/.juju/current-environment)
juju destroy-environment $jujuenv
juju destroy-environment ${jujuenv} ${force} || true
}
function detect-master() {
local kubestatus
# Capturing a newline, and my awk-fu was weak - pipe through tr -d
kubestatus=$(juju status --format=oneline kubernetes-master | grep kubernetes-master/0 | awk '{print $3}' | tr -d "\n")
export KUBE_MASTER_IP=${kubestatus}
export KUBE_MASTER=${KUBE_MASTER_IP}
export KUBERNETES_MASTER=http://${KUBE_MASTER}:8080
echo "Kubernetes master: " ${KUBERNETES_MASTER}
}
function detect-minions() {
# Run the Juju command that gets the minion private IP addresses.
local ipoutput
ipoutput=$(juju run --service kubernetes "unit-get private-address" --format=json)
echo $ipoutput
# Strip out the IP addresses
#
# Example Output:
#- MachineId: "10"
# Stdout: |
# 10.197.55.232
# UnitId: kubernetes/0
# - MachineId: "11"
# Stdout: |
# 10.202.146.124
# UnitId: kubernetes/1
export KUBE_MINION_IP_ADDRESSES=($(${JUJU_PATH}/return-node-ips.py "${ipoutput}"))
echo "Kubernetes minions: " ${KUBE_MINION_IP_ADDRESSES[@]}
export NUM_MINIONS=${#KUBE_MINION_IP_ADDRESSES[@]}
export MINION_NAMES=$KUBE_MINION_IP_ADDRESSES
}
function setup-logging-firewall() {
echo "TODO: setup logging and firewall rules"
}
function teardown-logging-firewall() {
echo "TODO: teardown logging and firewall rules"
function prepare-e2e() {
echo "prepare-e2e() The Juju provider does not need any preperations for e2e." 1>&2
}
function sleep-status() {
@ -115,7 +138,7 @@ function sleep-status() {
i=0
maxtime=900
jujustatus=''
echo "Waiting up to 15 minutes to allow the cluster to come online... wait for it..."
echo "Waiting up to 15 minutes to allow the cluster to come online... wait for it..." 1>&2
jujustatus=$(juju status kubernetes-master --format=oneline)
if [[ $jujustatus == *"started"* ]];
@ -132,6 +155,28 @@ function sleep-status() {
# 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"
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.
function test-build-release {
echo "test-build-release() " 1>&2
}
# Execute prior to running tests to initialize required structure. This is
# called from hack/e2e.go only when running -up (it is run after kube-up).
function test-setup {
echo "test-setup() " 1>&2
}
# Execute after running tests to perform any required clean-up. This is called
# from hack/e2e.go
function test-teardown() {
kube-down "-y"
}
# Verify the prerequisites are statisfied before running.
function verify-prereqs() {
gather_installation_reqs
}