mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-30 21:30:16 +00:00 
			
		
		
		
	The image reached end-of-life in October 2017. Remove the associated references from the cluster setup script.
		
			
				
	
	
		
			254 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env bash
 | |
| 
 | |
| # Copyright 2016 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.
 | |
| 
 | |
| # This script performs disaster recovery of etcd from the backup data.
 | |
| # Assumptions:
 | |
| # - backup was done using etcdctl command:
 | |
| #   a) in case of etcd2
 | |
| #      $ etcdctl backup --data-dir=<dir>
 | |
| #      produced .snap and .wal files
 | |
| #   b) in case of etcd3
 | |
| #      $ etcdctl --endpoints=<address> snapshot save
 | |
| #      produced .db file
 | |
| # - version.txt file is in the current directory (if it isn't it will be
 | |
| #     defaulted to "2.2.1/etcd2"). Based on this file, the script will
 | |
| #     decide to which version we are restoring (procedures are different
 | |
| #     for etcd2 and etcd3).
 | |
| # - in case of etcd2 - *.snap and *.wal files are in current directory
 | |
| # - in case of etcd3 - *.db file is in the current directory
 | |
| # - the script is run as root
 | |
| # - for event etcd, we only support clearing it - to do it, you need to
 | |
| #   set RESET_EVENT_ETCD=true env var.
 | |
| 
 | |
| set -o errexit
 | |
| set -o nounset
 | |
| set -o pipefail
 | |
| 
 | |
| # Version file contains information about current version in the format:
 | |
| # <etcd binary version>/<etcd api mode> (e.g. "3.0.12/etcd3").
 | |
| #
 | |
| # If the file doesn't exist we assume "2.2.1/etcd2" configuration is
 | |
| # the current one and create a file with such configuration.
 | |
| # The restore procedure is chosen based on this information.
 | |
| VERSION_FILE="version.txt"
 | |
| 
 | |
| # Make it possible to overwrite version file (or default version)
 | |
| # with VERSION_CONTENTS env var.
 | |
| if [ -n "${VERSION_CONTENTS:-}" ]; then
 | |
|   echo "${VERSION_CONTENTS}" > "${VERSION_FILE}"
 | |
| fi
 | |
| if [ ! -f "${VERSION_FILE}" ]; then
 | |
|   echo "2.2.1/etcd2" > "${VERSION_FILE}"
 | |
| fi
 | |
| VERSION_CONTENTS="$(cat ${VERSION_FILE})"
 | |
| ETCD_VERSION="$(echo $VERSION_CONTENTS | cut -d '/' -f 1)"
 | |
| ETCD_API="$(echo $VERSION_CONTENTS | cut -d '/' -f 2)"
 | |
| 
 | |
| # Name is used only in case of etcd3 mode, to appropriate set the metadata
 | |
| # for the etcd data.
 | |
| # NOTE: NAME HAS TO BE EQUAL TO WHAT WE USE IN --name flag when starting etcd.
 | |
| NAME="${NAME:-etcd-$(hostname)}"
 | |
| 
 | |
| INITIAL_CLUSTER="${INITIAL_CLUSTER:-${NAME}=http://localhost:2380}"
 | |
| INITIAL_ADVERTISE_PEER_URLS="${INITIAL_ADVERTISE_PEER_URLS:-http://localhost:2380}"
 | |
| 
 | |
| # Port on which etcd is exposed.
 | |
| etcd_port=2379
 | |
| event_etcd_port=4002
 | |
| 
 | |
| # Wait until both etcd instances are up
 | |
| wait_for_etcd_up() {
 | |
|   port=$1
 | |
|   # TODO: As of 3.0.x etcd versions, all 2.* and 3.* versions return
 | |
|   # {"health": "true"} on /health endpoint in healthy case.
 | |
|   # However, we should come with a regex for it to avoid future break.
 | |
|   health_ok="{\"health\": \"true\"}"
 | |
|   for i in $(seq 120); do
 | |
|     # TODO: Is it enough to look into /health endpoint?
 | |
|     health=$(curl --silent http://127.0.0.1:${port}/health)
 | |
|     if [ "${health}" == "${health_ok}" ]; then
 | |
|       return 0
 | |
|     fi
 | |
|     sleep 1
 | |
|   done
 | |
|   return 1
 | |
| }
 | |
| 
 | |
| # Wait until apiserver is up.
 | |
| wait_for_cluster_healthy() {
 | |
|   for i in $(seq 120); do
 | |
|     cs_status=$(kubectl get componentstatuses -o template --template='{{range .items}}{{with index .conditions 0}}{{.type}}:{{.status}}{{end}}{{"\n"}}{{end}}') || true
 | |
|     componentstatuses=$(echo "${cs_status}" | grep -c 'Healthy:') || true
 | |
|     healthy=$(echo "${cs_status}" | grep -c 'Healthy:True') || true
 | |
|     if [ "${componentstatuses}" -eq "${healthy}" ]; then
 | |
|       return 0
 | |
|     fi
 | |
|     sleep 1
 | |
|   done
 | |
|   return 1
 | |
| }
 | |
| 
 | |
| # Wait until etcd and apiserver pods are down.
 | |
| wait_for_etcd_and_apiserver_down() {
 | |
|   for i in $(seq 120); do
 | |
|     etcd=$(docker ps | grep etcd-server | wc -l)
 | |
|     apiserver=$(docker ps | grep apiserver | wc -l)
 | |
|     # TODO: Theoretically it is possible, that apiserver and or etcd
 | |
|     # are currently down, but Kubelet is now restarting them and they
 | |
|     # will reappear again. We should avoid it.
 | |
|     if [ "${etcd}" -eq "0" -a "${apiserver}" -eq "0" ]; then
 | |
|       return 0
 | |
|     fi
 | |
|     sleep 1
 | |
|   done
 | |
|   return 1
 | |
| }
 | |
| 
 | |
| # Move the manifest files to stop etcd and kube-apiserver
 | |
| # while we swap the data out from under them.
 | |
| MANIFEST_DIR="/etc/kubernetes/manifests"
 | |
| MANIFEST_BACKUP_DIR="/etc/kubernetes/manifests-backups"
 | |
| mkdir -p "${MANIFEST_BACKUP_DIR}"
 | |
| echo "Moving etcd(s) & apiserver manifest files to ${MANIFEST_BACKUP_DIR}"
 | |
| # If those files were already moved (e.g. during previous
 | |
| # try of backup) don't fail on it.
 | |
| mv "${MANIFEST_DIR}/kube-apiserver.manifest" "${MANIFEST_BACKUP_DIR}" || true
 | |
| mv "${MANIFEST_DIR}/etcd.manifest" "${MANIFEST_BACKUP_DIR}" || true
 | |
| mv "${MANIFEST_DIR}/etcd-events.manifest" "${MANIFEST_BACKUP_DIR}" || true
 | |
| 
 | |
| # Wait for the pods to be stopped
 | |
| echo "Waiting for etcd and kube-apiserver to be down"
 | |
| if ! wait_for_etcd_and_apiserver_down; then
 | |
|   # Couldn't kill etcd and apiserver.
 | |
|   echo "Downing etcd and apiserver failed"
 | |
|   exit 1
 | |
| fi
 | |
| 
 | |
| read -rsp $'Press enter when all etcd instances are down...\n'
 | |
| 
 | |
| # Create the sort of directory structure that etcd expects.
 | |
| # If this directory already exists, remove it.
 | |
| BACKUP_DIR="/var/tmp/backup"
 | |
| rm -rf "${BACKUP_DIR}"
 | |
| if [ "${ETCD_API}" == "etcd2" ]; then
 | |
|   echo "Preparing etcd backup data for restore"
 | |
|   # In v2 mode, we simply copy both snap and wal files to a newly created
 | |
|   # directory. After that, we start etcd with --force-new-cluster option
 | |
|   # that (according to the etcd documentation) is required to recover from
 | |
|   # a backup.
 | |
|   echo "Copying data to ${BACKUP_DIR} and restoring there"
 | |
|   mkdir -p "${BACKUP_DIR}/member/snap"
 | |
|   mkdir -p "${BACKUP_DIR}/member/wal"
 | |
|   # If the cluster is relatively new, there can be no .snap file.
 | |
|   mv *.snap "${BACKUP_DIR}/member/snap/" || true
 | |
|   mv *.wal "${BACKUP_DIR}/member/wal/"
 | |
| 
 | |
|   # TODO(jsz): This won't work with HA setups (e.g. do we need to set --name flag)?
 | |
|   echo "Starting etcd ${ETCD_VERSION} to restore data"
 | |
|   image=$(docker run -d -v ${BACKUP_DIR}:/var/etcd/data \
 | |
|     --net=host -p ${etcd_port}:${etcd_port} \
 | |
|     "k8s.gcr.io/etcd:${ETCD_VERSION}" /bin/sh -c \
 | |
|     "/usr/local/bin/etcd --data-dir /var/etcd/data --force-new-cluster")
 | |
|   if [ "$?" -ne "0" ]; then
 | |
|     echo "Docker container didn't started correctly"
 | |
|     exit 1
 | |
|   fi
 | |
|   echo "Container ${image} created, waiting for etcd to report as healthy"
 | |
| 
 | |
|   if ! wait_for_etcd_up "${etcd_port}"; then
 | |
|     echo "Etcd didn't come back correctly"
 | |
|     exit 1
 | |
|   fi
 | |
| 
 | |
|   # Kill that etcd instance.
 | |
|   echo "Etcd healthy - killing ${image} container"
 | |
|   docker kill "${image}"
 | |
| elif [ "${ETCD_API}" == "etcd3" ]; then
 | |
|   echo "Preparing etcd snapshot for restore"
 | |
|   mkdir -p "${BACKUP_DIR}"
 | |
|   echo "Copying data to ${BACKUP_DIR} and restoring there"
 | |
|   number_files=$(find . -maxdepth 1 -type f -name "*.db" | wc -l)
 | |
|   if [ "${number_files}" -ne "1" ]; then
 | |
|     echo "Incorrect number of *.db files - expected 1"
 | |
|     exit 1
 | |
|   fi
 | |
|   mv *.db "${BACKUP_DIR}/"
 | |
|   snapshot="$(ls ${BACKUP_DIR})"
 | |
| 
 | |
|   # Run etcdctl snapshot restore command and wait until it is finished.
 | |
|   # setting with --name in the etcd manifest file and then it seems to work.
 | |
|   docker run -v ${BACKUP_DIR}:/var/tmp/backup --env ETCDCTL_API=3 \
 | |
|     "k8s.gcr.io/etcd:${ETCD_VERSION}" /bin/sh -c \
 | |
|     "/usr/local/bin/etcdctl snapshot restore ${BACKUP_DIR}/${snapshot} --name ${NAME} --initial-cluster ${INITIAL_CLUSTER} --initial-advertise-peer-urls ${INITIAL_ADVERTISE_PEER_URLS}; mv /${NAME}.etcd/member /var/tmp/backup/"
 | |
|   if [ "$?" -ne "0" ]; then
 | |
|     echo "Docker container didn't started correctly"
 | |
|     exit 1
 | |
|   fi
 | |
| 
 | |
|   rm -f "${BACKUP_DIR}/${snapshot}"
 | |
| fi
 | |
| # Also copy version.txt file.
 | |
| cp "${VERSION_FILE}" "${BACKUP_DIR}"
 | |
| 
 | |
| export MNT_DISK="/mnt/disks/master-pd"
 | |
| 
 | |
| # Save the corrupted data (clean directory if it is already non-empty).
 | |
| rm -rf "${MNT_DISK}/var/etcd-corrupted"
 | |
| mkdir -p "${MNT_DISK}/var/etcd-corrupted"
 | |
| echo "Saving corrupted data to ${MNT_DISK}/var/etcd-corrupted"
 | |
| mv /var/etcd/data "${MNT_DISK}/var/etcd-corrupted"
 | |
| 
 | |
| # Replace the corrupted data dir with the restored data.
 | |
| echo "Copying restored data to /var/etcd/data"
 | |
| mv "${BACKUP_DIR}" /var/etcd/data
 | |
| 
 | |
| if [ "${RESET_EVENT_ETCD:-}" == "true" ]; then
 | |
|   echo "Removing event-etcd corrupted data"
 | |
|   EVENTS_CORRUPTED_DIR="${MNT_DISK}/var/etcd-events-corrupted"
 | |
|   # Save the corrupted data (clean directory if it is already non-empty).
 | |
|   rm -rf "${EVENTS_CORRUPTED_DIR}"
 | |
|   mkdir -p "${EVENTS_CORRUPTED_DIR}"
 | |
|   mv /var/etcd/data-events "${EVENTS_CORRUPTED_DIR}"
 | |
| fi
 | |
| 
 | |
| # Start etcd and kube-apiserver again.
 | |
| echo "Restarting etcd and apiserver from restored snapshot"
 | |
| mv "${MANIFEST_BACKUP_DIR}"/* "${MANIFEST_DIR}/"
 | |
| rm -rf "${MANIFEST_BACKUP_DIR}"
 | |
| 
 | |
| # Verify that etcd is back.
 | |
| echo "Waiting for etcd to come back"
 | |
| if ! wait_for_etcd_up "${etcd_port}"; then
 | |
|   echo "Etcd didn't come back correctly"
 | |
|   exit 1
 | |
| fi
 | |
| 
 | |
| # Verify that event etcd is back.
 | |
| echo "Waiting for event etcd to come back"
 | |
| if ! wait_for_etcd_up "${event_etcd_port}"; then
 | |
|   echo "Event etcd didn't come back correctly"
 | |
|   exit 1
 | |
| fi
 | |
| 
 | |
| # Verify that kube-apiserver is back and cluster is healthy.
 | |
| echo "Waiting for apiserver to come back"
 | |
| if ! wait_for_cluster_healthy; then
 | |
|   echo "Apiserver didn't come back correctly"
 | |
|   exit 1
 | |
| fi
 | |
| 
 | |
| echo "Cluster successfully restored!"
 |