#!/bin/bash # Copyright 2014 The Kubernetes Authors. # # 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. # A library of helper functions that each provider hosting Kubernetes must implement to use cluster/kube-*.sh scripts. [ ! -z ${UTIL_SH_DEBUG+x} ] && set -x KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../.. readonly ROOT=$(dirname "${BASH_SOURCE}") source "$ROOT/${KUBE_CONFIG_FILE:-"config-default.sh"}" source "$KUBE_ROOT/cluster/common.sh" export LIBVIRT_DEFAULT_URI=qemu:///system export SERVICE_ACCOUNT_LOOKUP=${SERVICE_ACCOUNT_LOOKUP:-false} export ADMISSION_CONTROL=${ADMISSION_CONTROL:-NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota} readonly POOL=kubernetes readonly POOL_PATH=/var/lib/libvirt/images/kubernetes [ ! -d "${POOL_PATH}" ] && (echo "$POOL_PATH" does not exist ; exit 1 ) # join # Concatenates the list elements with the delimiter passed as first parameter # # Ex: join , a b c # -> a,b,c function join { local IFS="$1" shift echo "$*" } # Must ensure that the following ENV vars are set function detect-master { KUBE_MASTER_IP=$MASTER_IP KUBE_MASTER=$MASTER_NAME export KUBERNETES_MASTER=http://$KUBE_MASTER_IP:8080 echo "KUBE_MASTER_IP: $KUBE_MASTER_IP" echo "KUBE_MASTER: $KUBE_MASTER" } # Get node IP addresses and store in KUBE_NODE_IP_ADDRESSES[] function detect-nodes { KUBE_NODE_IP_ADDRESSES=("${NODE_IPS[@]}") } function generate_certs { node_names=("${@}") #Root-CA tempdir=$(mktemp -d) CA_KEY=${CA_KEY:-"$tempdir/ca-key.pem"} CA_CERT=${CA_CERT:-"$tempdir/ca.pem"} openssl genrsa -out "${CA_KEY}" 2048 2>/dev/null openssl req -x509 -new -nodes -key "${CA_KEY}" -days 10000 -out "${CA_CERT}" -subj "/CN=kube-ca" 2>/dev/null #API server key pair KUBE_KEY=${KUBE_KEY:-"$tempdir/apiserver-key.pem"} API_SERVER_CERT_REQ=${API_SERVER_CERT_REQ:-"$tempdir/apiserver.csr"} openssl genrsa -out "${KUBE_KEY}" 2048 2>/dev/null KUBERNETES_SVC=${SERVICE_CLUSTER_IP_RANGE%.*}.1 openssl req -new -key "${KUBE_KEY}" -out "${API_SERVER_CERT_REQ}" -subj "/CN=kube-apiserver" -config cluster/libvirt-coreos/openssl.cnf 2>/dev/null KUBE_CERT=${KUBE_CERT:-"$tempdir/apiserver.pem"} KUBERNETES_SVC=${SERVICE_CLUSTER_IP_RANGE%.*}.1 openssl x509 -req -in "${API_SERVER_CERT_REQ}" -CA "${CA_CERT}" -CAkey "${CA_KEY}" -CAcreateserial -out "${KUBE_CERT}" -days 365 -extensions v3_req -extfile cluster/libvirt-coreos/openssl.cnf 2>/dev/null #Copy apiserver and controller tsl assets mkdir -p "$POOL_PATH/kubernetes/certs" cp "${KUBE_CERT}" "$POOL_PATH/kubernetes/certs" cp "${KUBE_KEY}" "$POOL_PATH/kubernetes/certs" cp "${CA_CERT}" "$POOL_PATH/kubernetes/certs" #Generate nodes certificate for (( i = 0 ; i < $NUM_NODES ; i++ )); do openssl genrsa -out $tempdir/${node_names[$i]}-node-key.pem 2048 2>/dev/null cp "$tempdir/${node_names[$i]}-node-key.pem" "$POOL_PATH/kubernetes/certs" WORKER_IP=${NODE_IPS[$i]} openssl req -new -key $tempdir/${node_names[$i]}-node-key.pem -out $tempdir/${node_names[$i]}-node.csr -subj "/CN=${node_names[$i]}" -config cluster/libvirt-coreos/node-openssl.cnf 2>/dev/null WORKER_IP=${NODE_IPS[$i]} openssl x509 -req -in $tempdir/${node_names[$i]}-node.csr -CA "${CA_CERT}" -CAkey "${CA_KEY}" -CAcreateserial -out $tempdir/${node_names[$i]}-node.pem -days 365 -extensions v3_req -extfile cluster/libvirt-coreos/node-openssl.cnf 2>/dev/null cp "$tempdir/${node_names[$i]}-node.pem" "$POOL_PATH/kubernetes/certs" done echo "TLS assets generated..." } # Verify prereqs on host machine function verify-prereqs { if ! which virsh >/dev/null; then echo "Can't find virsh in PATH, please fix and retry." >&2 exit 1 fi if ! virsh nodeinfo >/dev/null; then exit 1 fi if [[ "$(&2 echo "Enabling it would reduce the memory footprint of large clusters" >&2 if [[ -t 0 ]]; then read -t 5 -n 1 -p "Do you want to enable KSM (requires root password) (y/n)? " answer echo "" if [[ "$answer" == 'y' ]]; then su -c 'echo 1 > /sys/kernel/mm/ksm/run' fi else echo "You can enable it with (as root):" >&2 echo "" >&2 echo " echo 1 > /sys/kernel/mm/ksm/run" >&2 echo "" >&2 fi fi } # Destroy the libvirt storage pool and all the images inside # # If 'keep_base_image' is passed as first parameter, # the base image is kept, as well as the storage pool. # All the other images are deleted. function destroy-pool { virsh pool-info $POOL >/dev/null 2>&1 || return rm -rf "$POOL_PATH"/kubernetes/* rm -rf "$POOL_PATH"/kubernetes_config*/* local vol virsh vol-list $POOL | awk 'NR>2 && !/^$/ && $1 ~ /^kubernetes/ {print $1}' | \ while read vol; do virsh vol-delete $vol --pool $POOL done [[ "$1" == 'keep_base_image' ]] && return set +e virsh vol-delete coreos_base.img --pool $POOL virsh pool-destroy $POOL rmdir "$POOL_PATH" set -e } # Creates the libvirt storage pool and populate it with # - the CoreOS base image # - the kubernetes binaries function initialize-pool { mkdir -p "$POOL_PATH" if ! virsh pool-info $POOL >/dev/null 2>&1; then virsh pool-create-as $POOL dir --target "$POOL_PATH" fi wget -N -P "$ROOT" https://${COREOS_CHANNEL:-alpha}.release.core-os.net/amd64-usr/current/coreos_production_qemu_image.img.bz2 if [[ "$ROOT/coreos_production_qemu_image.img.bz2" -nt "$POOL_PATH/coreos_base.img" ]]; then bunzip2 -f -k "$ROOT/coreos_production_qemu_image.img.bz2" virsh vol-delete coreos_base.img --pool $POOL 2> /dev/null || true fi if ! virsh vol-list $POOL | grep -q coreos_base.img; then virsh vol-create-as $POOL coreos_base.img 10G --format qcow2 virsh vol-upload coreos_base.img "$ROOT/coreos_production_qemu_image.img" --pool $POOL fi mkdir -p "$POOL_PATH/kubernetes" kube-push-internal mkdir -p "$POOL_PATH/kubernetes/manifests" if [[ "$ENABLE_NODE_LOGGING" == "true" ]]; then if [[ "$LOGGING_DESTINATION" == "elasticsearch" ]]; then cp "$KUBE_ROOT/cluster/saltbase/salt/fluentd-es/fluentd-es.manifest" "$POOL_PATH/kubernetes/manifests" elif [[ "$LOGGING_DESTINATION" == "gcp" ]]; then cp "$KUBE_ROOT/cluster/saltbase/salt/fluentd-gcp/fluentd-gcp.manifest" "$POOL_PATH/kubernetes/manifests" fi fi mkdir -p "$POOL_PATH/kubernetes/addons" if [[ "$ENABLE_CLUSTER_DNS" == "true" ]]; then render-template "$ROOT/namespace.yaml" > "$POOL_PATH/kubernetes/addons/namespace.yaml" render-template "$ROOT/skydns-svc.yaml" > "$POOL_PATH/kubernetes/addons/skydns-svc.yaml" render-template "$ROOT/skydns-rc.yaml" > "$POOL_PATH/kubernetes/addons/skydns-rc.yaml" fi virsh pool-refresh $POOL } function destroy-network { set +e virsh net-destroy kubernetes_global virsh net-destroy kubernetes_pods set -e } function initialize-network { virsh net-create "$ROOT/network_kubernetes_global.xml" virsh net-create "$ROOT/network_kubernetes_pods.xml" } function render-template { eval "echo \"$(cat $1)\"" } function wait-cluster-readiness { echo "Wait for cluster readiness" local kubectl="${KUBE_ROOT}/cluster/kubectl.sh" local timeout=120 while [[ $timeout -ne 0 ]]; do nb_ready_nodes=$("${kubectl}" get nodes -o go-template="{{range.items}}{{range.status.conditions}}{{.type}}{{end}}:{{end}}" --api-version=v1 2>/dev/null | tr ':' '\n' | grep -c Ready || true) echo "Nb ready nodes: $nb_ready_nodes / $NUM_NODES" if [[ "$nb_ready_nodes" -eq "$NUM_NODES" ]]; then return 0 fi timeout=$(($timeout-1)) sleep .5 done return 1 } # Instantiate a kubernetes cluster function kube-up { detect-master detect-nodes initialize-pool keep_base_image generate_certs "${NODE_NAMES[@]}" initialize-network readonly ssh_keys="$(cat ~/.ssh/*.pub | sed 's/^/ - /')" readonly kubernetes_dir="$POOL_PATH/kubernetes" local i for (( i = 0 ; i <= $NUM_NODES ; i++ )); do if [[ $i -eq $NUM_NODES ]]; then etcd2_initial_cluster[$i]="${MASTER_NAME}=http://${MASTER_IP}:2380" else etcd2_initial_cluster[$i]="${NODE_NAMES[$i]}=http://${NODE_IPS[$i]}:2380" fi done etcd2_initial_cluster=$(join , "${etcd2_initial_cluster[@]}") readonly machines=$(join , "${KUBE_NODE_IP_ADDRESSES[@]}") for (( i = 0 ; i <= $NUM_NODES ; i++ )); do if [[ $i -eq $NUM_NODES ]]; then type=master name=$MASTER_NAME public_ip=$MASTER_IP else type=node-$(printf "%02d" $i) name=${NODE_NAMES[$i]} public_ip=${NODE_IPS[$i]} fi image=$name.img config=kubernetes_config_$type virsh vol-create-as $POOL $image 10G --format qcow2 --backing-vol coreos_base.img --backing-vol-format qcow2 mkdir -p "$POOL_PATH/$config/openstack/latest" render-template "$ROOT/user_data.yml" > "$POOL_PATH/$config/openstack/latest/user_data" virsh pool-refresh $POOL domain_xml=$(mktemp) render-template $ROOT/coreos.xml > $domain_xml virsh create $domain_xml rm $domain_xml done export KUBE_SERVER="http://192.168.10.1:8080" export CONTEXT="libvirt-coreos" create-kubeconfig wait-cluster-readiness echo "Kubernetes cluster is running. The master is running at:" echo echo " http://${KUBE_MASTER_IP}:8080" echo echo "You can control the Kubernetes cluster with: 'cluster/kubectl.sh'" echo "You can connect on the master with: 'ssh core@${KUBE_MASTER_IP}'" } # Delete a kubernetes cluster function kube-down { virsh list | awk 'NR>2 && !/^$/ && $2 ~ /^kubernetes/ {print $2}' | \ while read dom; do virsh destroy $dom done destroy-pool keep_base_image destroy-network } # The kubernetes binaries are pushed to a host directory which is exposed to the VM function upload-server-tars { tar -x -C "$POOL_PATH/kubernetes" -f "$SERVER_BINARY_TAR" kubernetes rm -rf "$POOL_PATH/kubernetes/bin" mv "$POOL_PATH/kubernetes/kubernetes/server/bin" "$POOL_PATH/kubernetes/bin" rm -fr "$POOL_PATH/kubernetes/kubernetes" } # Update a kubernetes cluster with latest source function kube-push { kube-push-internal ssh-to-node "$MASTER_NAME" "sudo systemctl restart kube-apiserver kube-controller-manager kube-scheduler" for ((i=0; i < NUM_NODES; i++)); do ssh-to-node "${NODE_NAMES[$i]}" "sudo systemctl restart kubelet kube-proxy" done wait-cluster-readiness } function kube-push-internal { case "${KUBE_PUSH:-release}" in release) kube-push-release;; local) kube-push-local;; *) echo "The only known push methods are \"release\" to use the release tarball or \"local\" to use the binaries built by make. KUBE_PUSH is set \"$KUBE_PUSH\"" >&2 return 1;; esac } function kube-push-release { find-release-tars upload-server-tars } function kube-push-local { rm -rf "$POOL_PATH/kubernetes/bin/*" mkdir -p "$POOL_PATH/kubernetes/bin" cp "${KUBE_ROOT}/_output/local/go/bin"/* "$POOL_PATH/kubernetes/bin" } # Execute prior to running tests to build a release if required for env function test-build-release { echo "TODO" } # Execute prior to running tests to initialize required structure function test-setup { "${KUBE_ROOT}/cluster/kube-up.sh" } # Execute after running tests to perform any required clean-up function test-teardown { kube-down } # SSH to a node by name or IP ($1) and run a command ($2). function ssh-to-node { local node="$1" local cmd="$2" local machine if [[ "$node" == "$MASTER_IP" ]] || [[ "$node" =~ ^"$NODE_IP_BASE" ]]; then machine="$node" elif [[ "$node" == "$MASTER_NAME" ]]; then machine="$MASTER_IP" else for ((i=0; i < NUM_NODES; i++)); do if [[ "$node" == "${NODE_NAMES[$i]}" ]]; then machine="${NODE_IPS[$i]}" break fi done fi if [[ -z "$machine" ]]; then echo "$node is an unknown machine to ssh to" >&2 fi ssh -o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=no "core@$machine" "$cmd" } # Perform preparations required to run e2e tests function prepare-e2e() { echo "libvirt-coreos doesn't need special preparations for e2e tests" 1>&2 }