mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 13:37:30 +00:00
Merge pull request #22726 from chuckbutler/juju-import-layers
Auto commit by PR queue bot
This commit is contained in:
commit
69b3cb36a6
3
.gitignore
vendored
3
.gitignore
vendored
@ -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/
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
|
1
cluster/juju/charms/trusty/.gitignore
vendored
1
cluster/juju/charms/trusty/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/docker
|
|
@ -1 +0,0 @@
|
|||||||
.git
|
|
@ -1,5 +0,0 @@
|
|||||||
*~
|
|
||||||
.bzr
|
|
||||||
.venv
|
|
||||||
unit_tests/__pycache__
|
|
||||||
*.pyc
|
|
@ -1,5 +0,0 @@
|
|||||||
omit:
|
|
||||||
- .git
|
|
||||||
- .gitignore
|
|
||||||
- .gitmodules
|
|
||||||
- revision
|
|
@ -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
|
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
[]()
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -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.
|
|
||||||
|
|
@ -1 +0,0 @@
|
|||||||
hooks.py
|
|
@ -1 +0,0 @@
|
|||||||
hooks.py
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||||||
install.py
|
|
@ -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()
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||||||
hooks.py
|
|
@ -1 +0,0 @@
|
|||||||
hooks.py
|
|
@ -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'])
|
|
@ -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
|
|
@ -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
|
|
@ -1,5 +0,0 @@
|
|||||||
flake8
|
|
||||||
pytest
|
|
||||||
bundletester
|
|
||||||
path.py
|
|
||||||
charmhelpers
|
|
@ -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()
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||||||
.git
|
|
@ -1,6 +0,0 @@
|
|||||||
.bzr
|
|
||||||
*.pyc
|
|
||||||
*~
|
|
||||||
*\#*
|
|
||||||
/files/.kubernetes-*
|
|
||||||
.venv
|
|
@ -1,5 +0,0 @@
|
|||||||
omit:
|
|
||||||
- .git
|
|
||||||
- .gitignore
|
|
||||||
- .gitmodules
|
|
||||||
- revision
|
|
@ -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
|
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
[]()
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||||||
hooks.py
|
|
@ -1 +0,0 @@
|
|||||||
hooks.py
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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.
|
|
||||||
|
|
@ -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")
|
|
@ -1 +0,0 @@
|
|||||||
hooks.py
|
|
@ -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 |
@ -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
|
|
@ -1 +0,0 @@
|
|||||||
path.py
|
|
@ -1,4 +0,0 @@
|
|||||||
flake8
|
|
||||||
pytest
|
|
||||||
bundletester
|
|
||||||
path.py
|
|
@ -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)
|
|
21
cluster/juju/charms/trusty/kubernetes/unit_tests/test_hooks.py → cluster/juju/identify-leaders.py
Normal file → Executable file
21
cluster/juju/charms/trusty/kubernetes/unit_tests/test_hooks.py → cluster/juju/identify-leaders.py
Normal file → Executable 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
|
77
cluster/juju/layers/kubernetes/README.md
Normal file
77
cluster/juju/layers/kubernetes/README.md
Normal 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 <Matthew.Bruzek@canonical.com>
|
||||||
|
* Charm Contributor: Charles Butler <Charles.Butler@canonical.com>
|
||||||
|
|
||||||
|
|
||||||
|
[]()
|
2
cluster/juju/layers/kubernetes/actions.yaml
Normal file
2
cluster/juju/layers/kubernetes/actions.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
guestbook-example:
|
||||||
|
description: Launch the guestbook example in your k8s cluster
|
35
cluster/juju/layers/kubernetes/actions/guestbook-example
Executable file
35
cluster/juju/layers/kubernetes/actions/guestbook-example
Executable 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
|
||||||
|
|
14
cluster/juju/layers/kubernetes/config.yaml
Normal file
14
cluster/juju/layers/kubernetes/config.yaml
Normal 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
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
1
cluster/juju/layers/kubernetes/layer.yaml
Normal file
1
cluster/juju/layers/kubernetes/layer.yaml
Normal file
@ -0,0 +1 @@
|
|||||||
|
includes: ['layer:docker', 'layer:flannel', 'layer:tls', 'interface:etcd']
|
17
cluster/juju/layers/kubernetes/metadata.yaml
Normal file
17
cluster/juju/layers/kubernetes/metadata.yaml
Normal 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
|
337
cluster/juju/layers/kubernetes/reactive/k8s.py
Normal file
337
cluster/juju/layers/kubernetes/reactive/k8s.py
Normal 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)
|
78
cluster/juju/layers/kubernetes/templates/docker-compose.yml
Normal file
78
cluster/juju/layers/kubernetes/templates/docker-compose.yml
Normal 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
|
61
cluster/juju/layers/kubernetes/templates/master.json
Normal file
61
cluster/juju/layers/kubernetes/templates/master.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
92
cluster/juju/layers/kubernetes/templates/skydns-rc.yml
Normal file
92
cluster/juju/layers/kubernetes/templates/skydns-rc.yml
Normal 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.
|
20
cluster/juju/layers/kubernetes/templates/skydns-svc.yml
Normal file
20
cluster/juju/layers/kubernetes/templates/skydns-svc.yml
Normal 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
|
@ -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'
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user