Support for creation/removes of master replicas.

HA master: initial support for creation/removal of masters replicas by
kube-up/kube-down scripts for GCE on gci.
This commit is contained in:
Jerzy Szczepkowski 2016-08-02 09:08:05 +02:00
parent fa6bd4b832
commit 05a41623b0
2 changed files with 248 additions and 35 deletions

View File

@ -37,8 +37,32 @@ function create-master-instance {
write-master-env write-master-env
ensure-gci-metadata-files ensure-gci-metadata-files
gcloud compute instances create "${MASTER_NAME}" \ create-master-instance-internal "${MASTER_NAME}" "${address_opt}"
${address_opt} \ }
function replicate-master-instance() {
local existing_master_zone="${1}"
local existing_master_name="${2}"
local existing_master_replicas="${3}"
local kube_env="$(get-metadata "${existing_master_zone}" "${existing_master_name}" kube-env)"
# Substitute INITIAL_ETCD_CLUSTER to enable etcd clustering.
kube_env="$(echo "${kube_env}" | grep -v "INITIAL_ETCD_CLUSTER")"
kube_env="$(echo -e "${kube_env}\nINITIAL_ETCD_CLUSTER: '${existing_master_replicas},${REPLICA_NAME}'")"
echo "${kube_env}" > ${KUBE_TEMP}/master-kube-env.yaml
get-metadata "${existing_master_zone}" "${existing_master_name}" cluster-name > "${KUBE_TEMP}/cluster-name.txt"
get-metadata "${existing_master_zone}" "${existing_master_name}" gci-update-strategy > "${KUBE_TEMP}/gci-update.txt"
get-metadata "${existing_master_zone}" "${existing_master_name}" gci-ensure-gke-docker > "${KUBE_TEMP}/gci-ensure-gke-docker.txt"
get-metadata "${existing_master_zone}" "${existing_master_name}" gci-docker-version > "${KUBE_TEMP}/gci-docker-version.txt"
create-master-instance-internal "${REPLICA_NAME}"
}
function create-master-instance-internal() {
local -r master_name="${1}"
local -r address_option="${2:-}"
gcloud compute instances create "${master_name}" \
${address_option} \
--project "${PROJECT}" \ --project "${PROJECT}" \
--zone "${ZONE}" \ --zone "${ZONE}" \
--machine-type "${MASTER_SIZE}" \ --machine-type "${MASTER_SIZE}" \
@ -50,6 +74,16 @@ function create-master-instance {
--can-ip-forward \ --can-ip-forward \
--metadata-from-file \ --metadata-from-file \
"kube-env=${KUBE_TEMP}/master-kube-env.yaml,user-data=${KUBE_ROOT}/cluster/gce/gci/master.yaml,configure-sh=${KUBE_ROOT}/cluster/gce/gci/configure.sh,cluster-name=${KUBE_TEMP}/cluster-name.txt,gci-update-strategy=${KUBE_TEMP}/gci-update.txt,gci-ensure-gke-docker=${KUBE_TEMP}/gci-ensure-gke-docker.txt,gci-docker-version=${KUBE_TEMP}/gci-docker-version.txt" \ "kube-env=${KUBE_TEMP}/master-kube-env.yaml,user-data=${KUBE_ROOT}/cluster/gce/gci/master.yaml,configure-sh=${KUBE_ROOT}/cluster/gce/gci/configure.sh,cluster-name=${KUBE_TEMP}/cluster-name.txt,gci-update-strategy=${KUBE_TEMP}/gci-update.txt,gci-ensure-gke-docker=${KUBE_TEMP}/gci-ensure-gke-docker.txt,gci-docker-version=${KUBE_TEMP}/gci-docker-version.txt" \
--disk "name=${MASTER_NAME}-pd,device-name=master-pd,mode=rw,boot=no,auto-delete=no" \ --disk "name=${master_name}-pd,device-name=master-pd,mode=rw,boot=no,auto-delete=no" \
--boot-disk-size "${MASTER_ROOT_DISK_SIZE:-10}" --boot-disk-size "${MASTER_ROOT_DISK_SIZE:-10}"
} }
function get-metadata() {
local zone="${1}"
local name="${2}"
local key="${3}"
gcloud compute ssh "${name}" \
--project "${PROJECT}" \
--zone "${zone}" \
--command "curl \"http://metadata.google.internal/computeMetadata/v1/instance/attributes/${key}\" -H \"Metadata-Flavor: Google\"" 2>/dev/null
}

View File

@ -98,17 +98,17 @@ KUBE_SKIP_UPDATE=${KUBE_SKIP_UPDATE-"n"}
# How long (in seconds) to wait for cluster initialization. # How long (in seconds) to wait for cluster initialization.
KUBE_CLUSTER_INITIALIZATION_TIMEOUT=${KUBE_CLUSTER_INITIALIZATION_TIMEOUT:-300} KUBE_CLUSTER_INITIALIZATION_TIMEOUT=${KUBE_CLUSTER_INITIALIZATION_TIMEOUT:-300}
function join_csv { function join_csv() {
local IFS=','; echo "$*"; local IFS=','; echo "$*";
} }
# This function returns the first string before the comma # This function returns the first string before the comma
function split_csv { function split_csv() {
echo "$*" | cut -d',' -f1 echo "$*" | cut -d',' -f1
} }
# Verify prereqs # Verify prereqs
function verify-prereqs { function verify-prereqs() {
local cmd local cmd
for cmd in gcloud gsutil; do for cmd in gcloud gsutil; do
if ! which "${cmd}" >/dev/null; then if ! which "${cmd}" >/dev/null; then
@ -149,7 +149,7 @@ function verify-prereqs {
# #
# Vars set: # Vars set:
# KUBE_TEMP # KUBE_TEMP
function ensure-temp-dir { function ensure-temp-dir() {
if [[ -z ${KUBE_TEMP-} ]]; then if [[ -z ${KUBE_TEMP-} ]]; then
KUBE_TEMP=$(mktemp -d -t kubernetes.XXXXXX) KUBE_TEMP=$(mktemp -d -t kubernetes.XXXXXX)
trap 'rm -rf "${KUBE_TEMP}"' EXIT trap 'rm -rf "${KUBE_TEMP}"' EXIT
@ -162,7 +162,7 @@ function ensure-temp-dir {
# Vars set: # Vars set:
# PROJECT # PROJECT
# PROJECT_REPORTED # PROJECT_REPORTED
function detect-project () { function detect-project() {
if [[ -z "${PROJECT-}" ]]; then if [[ -z "${PROJECT-}" ]]; then
PROJECT=$(gcloud config list project --format 'value(core.project)') PROJECT=$(gcloud config list project --format 'value(core.project)')
fi fi
@ -328,7 +328,7 @@ function upload-server-tars() {
# Vars set: # Vars set:
# NODE_NAMES # NODE_NAMES
# INSTANCE_GROUPS # INSTANCE_GROUPS
function detect-node-names { function detect-node-names() {
detect-project detect-project
INSTANCE_GROUPS=() INSTANCE_GROUPS=()
INSTANCE_GROUPS+=($(gcloud compute instance-groups managed list \ INSTANCE_GROUPS+=($(gcloud compute instance-groups managed list \
@ -354,7 +354,7 @@ function detect-node-names {
# Vars set: # Vars set:
# NODE_NAMES # NODE_NAMES
# KUBE_NODE_IP_ADDRESSES (array) # KUBE_NODE_IP_ADDRESSES (array)
function detect-nodes () { function detect-nodes() {
detect-project detect-project
detect-node-names detect-node-names
KUBE_NODE_IP_ADDRESSES=() KUBE_NODE_IP_ADDRESSES=()
@ -383,7 +383,7 @@ function detect-nodes () {
# Vars set: # Vars set:
# KUBE_MASTER # KUBE_MASTER
# KUBE_MASTER_IP # KUBE_MASTER_IP
function detect-master () { function detect-master() {
detect-project detect-project
KUBE_MASTER=${MASTER_NAME} KUBE_MASTER=${MASTER_NAME}
if [[ -z "${KUBE_MASTER_IP-}" ]]; then if [[ -z "${KUBE_MASTER_IP-}" ]]; then
@ -413,7 +413,7 @@ function get-master-env() {
# Robustly try to create a static ip. # Robustly try to create a static ip.
# $1: The name of the ip to create # $1: The name of the ip to create
# $2: The name of the region to create the ip in. # $2: The name of the region to create the ip in.
function create-static-ip { function create-static-ip() {
detect-project detect-project
local attempt=0 local attempt=0
local REGION="$2" local REGION="$2"
@ -446,7 +446,7 @@ function create-static-ip {
# $1: The name of firewall rule. # $1: The name of firewall rule.
# $2: IP ranges. # $2: IP ranges.
# $3: Target tags for this firewall rule. # $3: Target tags for this firewall rule.
function create-firewall-rule { function create-firewall-rule() {
detect-project detect-project
local attempt=0 local attempt=0
while true; do while true; do
@ -470,7 +470,7 @@ function create-firewall-rule {
} }
# $1: version (required) # $1: version (required)
function get-template-name-from-version { function get-template-name-from-version() {
# trim template name to pass gce name validation # trim template name to pass gce name validation
echo "${NODE_INSTANCE_PREFIX}-template-${1}" | cut -c 1-63 | sed 's/[\.\+]/-/g;s/-*$//g' echo "${NODE_INSTANCE_PREFIX}-template-${1}" | cut -c 1-63 | sed 's/[\.\+]/-/g;s/-*$//g'
} }
@ -479,7 +479,7 @@ function get-template-name-from-version {
# $1: The name of the instance template. # $1: The name of the instance template.
# $2: The scopes flag. # $2: The scopes flag.
# $3 and others: Metadata entries (must all be from a file). # $3 and others: Metadata entries (must all be from a file).
function create-node-template { function create-node-template() {
detect-project detect-project
local template_name="$1" local template_name="$1"
@ -536,7 +536,7 @@ function create-node-template {
# Robustly try to add metadata on an instance. # Robustly try to add metadata on an instance.
# $1: The name of the instance. # $1: The name of the instance.
# $2...$n: The metadata key=value pairs to add. # $2...$n: The metadata key=value pairs to add.
function add-instance-metadata { function add-instance-metadata() {
local -r instance=$1 local -r instance=$1
shift 1 shift 1
local -r kvs=( "$@" ) local -r kvs=( "$@" )
@ -563,7 +563,7 @@ function add-instance-metadata {
# Robustly try to add metadata on an instance, from a file. # Robustly try to add metadata on an instance, from a file.
# $1: The name of the instance. # $1: The name of the instance.
# $2...$n: The metadata key=file pairs to add. # $2...$n: The metadata key=file pairs to add.
function add-instance-metadata-from-file { function add-instance-metadata-from-file() {
local -r instance=$1 local -r instance=$1
shift 1 shift 1
local -r kvs=( "$@" ) local -r kvs=( "$@" )
@ -593,7 +593,7 @@ function add-instance-metadata-from-file {
# Assumed vars # Assumed vars
# KUBE_ROOT # KUBE_ROOT
# <Various vars set in config file> # <Various vars set in config file>
function kube-up { function kube-up() {
ensure-temp-dir ensure-temp-dir
detect-project detect-project
@ -611,8 +611,17 @@ function kube-up {
parse-master-env parse-master-env
create-nodes create-nodes
elif [[ ${KUBE_REPLICATE_EXISTING_MASTER:-} == "true" ]]; then elif [[ ${KUBE_REPLICATE_EXISTING_MASTER:-} == "true" ]]; then
# TODO(jsz): implement adding replica for other distributions.
if [[ "${MASTER_OS_DISTRIBUTION}" != "gci" ]]; then
echo "Master replication supported only for gci"
return 1
fi
create-loadbalancer create-loadbalancer
# TODO: Add logic for copying an existing master. # If replication of master fails, we need to ensure that the replica is removed from etcd clusters.
if ! replicate-master; then
remove-replica-from-etcd 4001 || true
remove-replica-from-etcd 4002 || true
fi
else else
check-existing check-existing
create-network create-network
@ -732,6 +741,80 @@ function create-master() {
create-master-instance "${MASTER_RESERVED_IP}" & create-master-instance "${MASTER_RESERVED_IP}" &
} }
# Adds master replica to etcd cluster.
#
# Assumed vars:
# REPLICA_NAME
# PROJECT
# EXISTING_MASTER_NAME
# EXISTING_MASTER_ZONE
#
# $1: etcd client port
# $2: etcd internal port
# returns the result of ssh command which adds replica
function add-replica-to-etcd() {
local -r client_port="${1}"
local -r internal_port="${2}"
gcloud compute ssh "${EXISTING_MASTER_NAME}" \
--project "${PROJECT}" \
--zone "${EXISTING_MASTER_ZONE}" \
--command \
"curl localhost:${client_port}/v2/members -XPOST -H \"Content-Type: application/json\" -d '{\"peerURLs\":[\"http://${REPLICA_NAME}:${internal_port}\"]}'"
return $?
}
# Sets EXISTING_MASTER_NAME and EXISTING_MASTER_ZONE variables.
#
# Assumed vars:
# PROJECT
#
# NOTE: Must be in sync with get-replica-name-regexp
function set-existing-master() {
local existing_master=$(gcloud compute instances list \
--project "${PROJECT}" \
--regexp "$(get-replica-name-regexp)" \
--format "value(name,zone)" | head -n1)
EXISTING_MASTER_NAME="$(echo "${existing_master}" | cut -f1)"
EXISTING_MASTER_ZONE="$(echo "${existing_master}" | cut -f2)"
}
function replicate-master() {
set-replica-name
set-existing-master
echo "Replicating existing master ${EXISTING_MASTER_ZONE}/${EXISTING_MASTER_NAME} as ${ZONE}/${REPLICA_NAME}"
# Before we do anything else, we should configure etcd to expect more replicas.
if ! add-replica-to-etcd 4001 2380; then
echo "Failed to add master replica to etcd cluster."
return 1
fi
if ! add-replica-to-etcd 4002 2381; then
echo "Failed to add master replica to etcd events cluster."
return 1
fi
# We have to make sure the disk is created before creating the master VM, so
# run this in the foreground.
gcloud compute disks create "${REPLICA_NAME}-pd" \
--project "${PROJECT}" \
--zone "${ZONE}" \
--type "${MASTER_DISK_TYPE}" \
--size "${MASTER_DISK_SIZE}"
# Sets MASTER_ROOT_DISK_SIZE that is used by create-master-instance
get-master-root-disk-size
local existing_master_replicas="$(get-all-replica-names)"
replicate-master-instance "${EXISTING_MASTER_ZONE}" "${EXISTING_MASTER_NAME}" "${existing_master_replicas}"
# Add new replica to the load balancer.
gcloud compute target-pools add-instances "${MASTER_NAME}" \
--project "${PROJECT}" \
--zone "${ZONE}" \
--instances "${REPLICA_NAME}"
}
# Detaches old and ataches new external IP to a VM. # Detaches old and ataches new external IP to a VM.
# #
# Arguments: # Arguments:
@ -1022,6 +1105,26 @@ function check-cluster() {
} }
# Removes master replica from etcd cluster.
#
# Assumed vars:
# REPLICA_NAME
# PROJECT
# EXISTING_MASTER_NAME
# EXISTING_MASTER_ZONE
#
# $1: etcd client port
# returns the result of ssh command which removes replica
function remove-replica-from-etcd() {
local -r port="${1}"
gcloud compute ssh "${EXISTING_MASTER_NAME}" \
--project "${PROJECT}" \
--zone "${EXISTING_MASTER_ZONE}" \
--command \
"curl -s localhost:${port}/v2/members/\$(curl -s localhost:${port}/v2/members -XGET | sed 's/{\\\"id/\n/g' | grep ${REPLICA_NAME} | cut -f 3 -d \\\") -XDELETE -L 2>/dev/null"
return $?
}
# Delete a kubernetes cluster. This is called from test-teardown. # Delete a kubernetes cluster. This is called from test-teardown.
# #
# Assumed vars: # Assumed vars:
@ -1031,7 +1134,7 @@ function check-cluster() {
# This function tears down cluster resources 10 at a time to avoid issuing too many # This function tears down cluster resources 10 at a time to avoid issuing too many
# API calls and exceeding API quota. It is important to bring down the instances before bringing # API calls and exceeding API quota. It is important to bring down the instances before bringing
# down the firewall rules and routes. # down the firewall rules and routes.
function kube-down { function kube-down() {
local -r batch=200 local -r batch=200
detect-project detect-project
@ -1069,23 +1172,39 @@ function kube-down {
fi fi
done done
# First delete the master (if it exists). local -r REPLICA_NAME="$(get-replica-name)"
if gcloud compute instances describe "${MASTER_NAME}" --zone "${ZONE}" --project "${PROJECT}" &>/dev/null; then
set-existing-master
# Un-register the master replica from etcd and events etcd.
remove-replica-from-etcd 4001
remove-replica-from-etcd 4002
# Delete the master replica (if it exists).
if gcloud compute instances describe "${REPLICA_NAME}" --zone "${ZONE}" --project "${PROJECT}" &>/dev/null; then
# If there is a load balancer in front of apiservers we need to first update its configuration.
if gcloud compute target-pools describe "${MASTER_NAME}" --region "${REGION}" --project "${PROJECT}" &>/dev/null; then
gcloud compute target-pools remove-instances "${MASTER_NAME}" \
--project "${PROJECT}" \
--zone "${ZONE}" \
--instances "${REPLICA_NAME}"
fi
# Now we can safely delete the VM.
gcloud compute instances delete \ gcloud compute instances delete \
--project "${PROJECT}" \ --project "${PROJECT}" \
--quiet \ --quiet \
--delete-disks all \ --delete-disks all \
--zone "${ZONE}" \ --zone "${ZONE}" \
"${MASTER_NAME}" "${REPLICA_NAME}"
fi fi
# Delete the master pd (possibly leaked by kube-up if master create failed). # Delete the master replica pd (possibly leaked by kube-up if master create failed).
if gcloud compute disks describe "${MASTER_NAME}"-pd --zone "${ZONE}" --project "${PROJECT}" &>/dev/null; then if gcloud compute disks describe "${REPLICA_NAME}"-pd --zone "${ZONE}" --project "${PROJECT}" &>/dev/null; then
gcloud compute disks delete \ gcloud compute disks delete \
--project "${PROJECT}" \ --project "${PROJECT}" \
--quiet \ --quiet \
--zone "${ZONE}" \ --zone "${ZONE}" \
"${MASTER_NAME}"-pd "${REPLICA_NAME}"-pd
fi fi
# Delete disk for cluster registry if enabled # Delete disk for cluster registry if enabled
@ -1102,7 +1221,7 @@ function kube-down {
# Check if this are any remaining master replicas. # Check if this are any remaining master replicas.
local REMAINING_MASTER_COUNT=$(gcloud compute instances list \ local REMAINING_MASTER_COUNT=$(gcloud compute instances list \
--project "${PROJECT}" \ --project "${PROJECT}" \
--regexp "${MASTER_NAME}(-...)?" \ --regexp "$(get-replica-name-regexp)" \
--format "value(zone)" | wc -l) --format "value(zone)" | wc -l)
# In the replicated scenario, if there's only a single master left, we should also delete load balancer in front of it. # In the replicated scenario, if there's only a single master left, we should also delete load balancer in front of it.
@ -1206,13 +1325,73 @@ function kube-down {
set -e set -e
} }
# Prints name of one of the master replicas in the current zone. It will be either
# just MASTER_NAME or MASTER_NAME with a suffix for a replica (see get-replica-name-regexp).
#
# Assumed vars:
# PROJECT
# ZONE
# MASTER_NAME
#
# NOTE: Must be in sync with get-replica-name-regexp and set-replica-name.
function get-replica-name() {
echo $(gcloud compute instances list \
--project "${PROJECT}" \
--zone "${ZONE}" \
--regexp "$(get-replica-name-regexp)" \
--format "value(name)" | head -n1)
}
# Prints comma-separated names of all of the master replicas in all zones.
#
# Assumed vars:
# PROJECT
# MASTER_NAME
#
# NOTE: Must be in sync with get-replica-name-regexp and set-replica-name.
function get-all-replica-names() {
echo $(gcloud compute instances list \
--project "${PROJECT}" \
--regexp "$(get-replica-name-regexp)" \
--format "value(name)" | tr "\n" "," | sed 's/,$//')
}
# Prints regexp for full master machine name. In a cluster with replicated master,
# VM names may either be MASTER_NAME or MASTER_NAME with a suffix for a replica.
function get-replica-name-regexp() {
echo "${MASTER_NAME}(-...)?"
}
# Sets REPLICA_NAME to a unique name for a master replica that will match
# expected regexp (see get-replica-name-regexp).
#
# Assumed vars:
# PROJECT
# ZONE
# MASTER_NAME
#
# Sets:
# REPLICA_NAME
function set-replica-name() {
local instances=$(gcloud compute instances list \
--project "${PROJECT}" \
--regexp "$(get-replica-name-regexp)" \
--format "value(name)")
suffix=""
while echo "${instances}" | grep "${suffix}" &>/dev/null; do
suffix="$(date | md5sum | head -c3)"
done
REPLICA_NAME="${MASTER_NAME}-${suffix}"
}
# Gets the instance template for given NODE_INSTANCE_PREFIX. It echos the template name so that the function # Gets the instance template for given NODE_INSTANCE_PREFIX. It echos the template name so that the function
# output can be used. # output can be used.
# Assumed vars: # Assumed vars:
# NODE_INSTANCE_PREFIX # NODE_INSTANCE_PREFIX
# #
# $1: project # $1: project
function get-template { function get-template() {
gcloud compute instance-templates list -r "${NODE_INSTANCE_PREFIX}-template(-(${KUBE_RELEASE_VERSION_DASHED_REGEX}|${KUBE_CI_VERSION_DASHED_REGEX}))?" \ gcloud compute instance-templates list -r "${NODE_INSTANCE_PREFIX}-template(-(${KUBE_RELEASE_VERSION_DASHED_REGEX}|${KUBE_CI_VERSION_DASHED_REGEX}))?" \
--project="${1}" --format='value(name)' --project="${1}" --format='value(name)'
} }
@ -1226,7 +1405,7 @@ function get-template {
# REGION # REGION
# Vars set: # Vars set:
# KUBE_RESOURCE_FOUND # KUBE_RESOURCE_FOUND
function check-resources { function check-resources() {
detect-project detect-project
detect-node-names detect-node-names
@ -1372,7 +1551,7 @@ function prepare-push() {
} }
# Push binaries to kubernetes master # Push binaries to kubernetes master
function push-master { function push-master() {
echo "Updating master metadata ..." echo "Updating master metadata ..."
write-master-env write-master-env
prepare-startup-script prepare-startup-script
@ -1395,7 +1574,7 @@ function push-node() {
} }
# Push binaries to kubernetes cluster # Push binaries to kubernetes cluster
function kube-push { function kube-push() {
# Disable this until it's fixed. # Disable this until it's fixed.
# See https://github.com/kubernetes/kubernetes/issues/17397 # See https://github.com/kubernetes/kubernetes/issues/17397
echo "./cluster/kube-push.sh is currently not supported in GCE." echo "./cluster/kube-push.sh is currently not supported in GCE."
@ -1438,7 +1617,7 @@ function kube-push {
# #
# Assumed Vars: # Assumed Vars:
# KUBE_ROOT # KUBE_ROOT
function test-build-release { function test-build-release() {
# Make a release # Make a release
"${KUBE_ROOT}/build/release.sh" "${KUBE_ROOT}/build/release.sh"
} }
@ -1448,7 +1627,7 @@ function test-build-release {
# #
# Assumed vars: # Assumed vars:
# Variables from config.sh # Variables from config.sh
function test-setup { function test-setup() {
# Detect the project into $PROJECT if it isn't set # Detect the project into $PROJECT if it isn't set
detect-project detect-project
@ -1503,7 +1682,7 @@ function test-setup {
# Execute after running tests to perform any required clean-up. This is called # Execute after running tests to perform any required clean-up. This is called
# from hack/e2e.go # from hack/e2e.go
function test-teardown { function test-teardown() {
detect-project detect-project
echo "Shutting down test cluster in background." echo "Shutting down test cluster in background."
gcloud compute firewall-rules delete \ gcloud compute firewall-rules delete \
@ -1528,7 +1707,7 @@ function test-teardown {
} }
# SSH to a node by name ($1) and run a command ($2). # SSH to a node by name ($1) and run a command ($2).
function ssh-to-node { function ssh-to-node() {
local node="$1" local node="$1"
local cmd="$2" local cmd="$2"
# Loop until we can successfully ssh into the box # Loop until we can successfully ssh into the box