Skip to content

Instantly share code, notes, and snippets.

@gg7
Last active February 28, 2017 00:43
Show Gist options
  • Save gg7/3d632e77476235c020d9c434908accb1 to your computer and use it in GitHub Desktop.
Save gg7/3d632e77476235c020d9c434908accb1 to your computer and use it in GitHub Desktop.
Continuous delivery with Kubernetes

Continuous delivery with Kubernetes

"Official" way

https://github.com/kubernetes/contrib/blob/93071222670f44a18b65a98113b8f22eabcb3f14/continuousdelivery/deploy/deploy-service.sh

"rollout status" way

Doesn't work:

# This doesn't work in v1.5.2, try again in v1.5.3. You could also try using 'timeout'.
# https://github.com/kubernetes/kubernetes/issues/40496
# https://github.com/kubernetes/kubernetes/issues/40207
#
# rc=0
# kubectl rollout status --watch "deployment/$app_name" || rc=$?
# if [[ $rc -ne 0 ]]; then
# 	echo "Rolling back:" >&2
# 	echo kubectl rollout undo "deployment/${app_name}"
# 	exit 1
# fi

My way

#!/bin/bash

set -o errexit
set -o nounset
set -o pipefail
set -o xtrace

registry=${KUBERNETES_REGISTRY:-docker.local}

app=${KUBERNETES_APP:-}
if [[ -z "$app" ]]; then
	echo "KUBERNETES_APP should be defined (e.g. 'harry')" >&2
	exit 1
fi

namespace=${KUBERNETES_NAMESPACE:-}

timeout=${KUBERNETES_CI_TIMEOUT_SECONDS:-300}
case $timeout in
	''|*[!0-9]*) echo "KUBERNETES_CI_TIMEOUT_SECONDS should be a number" >&2 && exit 1 ;;
	*) true ;;
esac
if [[ $timeout -lt 10 ]]; then
	echo "KUBERNETES_CI_TIMEOUT_SECONDS should be at least 10" >&2 && exit 1
fi

kube_config=${KUBE_CONFIG:-config}
if [[ ! -e "$HOME/.kube/$kube_config" ]]; then
	echo "kube config does not exist: $HOME/.kube/$kube_config" >&2
	exit 1
fi

kubernetes_rollback_on_failure=${KUBERNETES_ROLLBACK_ON_FAILURE:-true}
if [[ $kubernetes_rollback_on_failure != true ]] && [[ $kubernetes_rollback_on_failure != false ]]; then
	echo "\$KUBERNETES_ROLLBACK_ON_FAILURE should be true or false" >&2
	exit 1
fi

image=${IMAGE:-$app}

image_tag=${IMAGE_TAG:-}
if [[ -z "$image_tag" ]]; then
	image_tag="$(git log -1 --date=short --pretty=format:%cd)-$(git rev-parse --short HEAD)"
fi

kubernetes_push_latest=${KUBERNETES_PUSH_LATEST:-false}
if [[ $kubernetes_push_latest != true ]] && [[ $kubernetes_push_latest != false ]]; then
	echo "\$KUBERNETES_PUSH_LATEST should be true or false" >&2
	exit 1
fi

k8s_push() {
	docker tag "$1" "$registry/$image:$image_tag"
	docker push "$registry/$image:$image_tag"
	if [[ $kubernetes_push_latest == true ]]; then
		docker tag "$1" "$registry/$image:latest"
		docker push "$registry/$image:latest"
	fi
}

kubectl_helper() {
	set -o errexit
	set -o nounset
	set -o pipefail

	if [[ -z "$namespace" ]]; then
		echo "KUBERNETES_NAMESPACE should be defined (e.g. 'prod' or 'staging')" >&2
		exit 1
	fi

	kubectl --kubeconfig "$HOME/.kube/$kube_config" --namespace "${app}-${namespace}" "$@"
}

k8s_describe() {
	set -o errexit
	set -o nounset
	set -o pipefail

	printf "\nCurrent deployment (short):\n"
	kubectl_helper get deployments -a -o wide
	printf "\nCurrent deployment (detailed):\n"
	kubectl_helper describe deployment "$app" || true
	printf "\nCurrent deployment (yaml):\n"
	kubectl_helper get deployment "$app" -o yaml || true
	printf "\nReplica sets:\n"
	kubectl_helper get replicasets -a -o wide
	printf "\nPods:\n"
	kubectl_helper get pods -a -o wide
}

k8s_update_tag() {
	set -o errexit
	set -o nounset
	set -o pipefail

	if [[ $# -ne 1 ]]; then
		echo "Usage: k8s_update_tag <deployment-file>" >&2
		exit 1
	fi

	file=$1

	# https://github.com/kubernetes/kubernetes/issues/33664
	sed -i.old -r "s|($registry/$image):@VERSION@|\\1:${image_tag}|" "$file"
	if diff "$file" "$file.old" >/dev/null; then
		echo "@VERSION@ not found in $file" >&2
		exit 1
	fi
	rm "$file.old"
	sed -i -r "/^\\s*#/d" "$file"
}

k8s_apply() {
	set -o errexit
	set -o nounset
	set -o pipefail

	# 1. This does not wait for the deployment to actually finish:
	#    https://github.com/kubernetes/kubernetes/issues/1899
	# 2. If you delete some properties you might have to use `replace` here
	# 3. You should not use `replace` here:
	#    https://github.com/kubernetes/kubernetes/issues/11237
	#    https://github.com/kubernetes/kubernetes/issues/35858
	kubectl_helper apply -f "$1"
}

k8s_watch_deploy() {
	set -o errexit
	set -o nounset
	set -o pipefail

	# kubectl_helper get deployment -o json | jq -r '[.spec.template.spec.containers[0].image] | join(",")'

	set +o xtrace

	final_exit_code=0
	start_time=$(date +%s)

	while true; do
		deployment_json=$(kubectl_helper get "deployment/${app}" -o json || (echo "get deployment failed!" && exit 1))

		replicas_spec=$(echo "$deployment_json" | jq -r ".spec.replicas" | grep -P '\d+' || (echo "Can't get .spec.replicas!" && exit 1))
		replicas_status_present=$(echo "$deployment_json" | jq -r ".status.replicas" | grep -P '\d+' || (echo "Can't get .status.replicas!" && exit 1))
		replicas_status_updated=$(echo "$deployment_json" | jq -r ".status.updatedReplicas" | grep -P '\d+' || echo 0)
		replicas_status_unavail=$(echo "$deployment_json" | jq -r ".status.unavailableReplicas" | grep -P '\d+' || echo 0)
		replicas_status_avail=$(echo "$deployment_json" | jq -r ".status.availableReplicas" | grep -P '\d+' || echo 0)
		progressing=$(echo "$deployment_json" | jq -r '.status.conditions[] | select(.type == "Progressing").status' || echo "unknown")

		printf "%s desired: %2d; present: %2d; available: %2d; updated: %2d; unavailable: %2d\n" \
			"$(date +%T)" \
			"$replicas_spec" \
			"$replicas_status_present" \
			"$replicas_status_avail" \
			"$replicas_status_updated" \
			"$replicas_status_unavail"

		if [[ $progressing == "False" ]] || [[ $(($(date +%s) - start_time)) -gt "$timeout" ]]; then
			if [[ $progressing == "False" ]]; then
				echo "We have stopped progressing!" >&2
				echo "$deployment_json" | jq '.status'
			else
				echo "Timed out after $timeout seconds!" >&2
			fi

			echo
			kubectl_helper describe deployment "${app}"

			if [[ $kubernetes_rollback_on_failure == true ]]; then
				echo
				echo "Rolling back:" >&2
				kubectl_helper rollout undo "deployment/${app}"
			fi

			final_exit_code=1
			break
		fi

		if [[ "$replicas_status_updated" -lt "$replicas_spec" ]] || \
				[[ "$replicas_status_avail" -lt "$replicas_spec" ]] || \
				[[ "$replicas_status_present" -lt "$replicas_spec" ]] || \
				[[ "$replicas_status_unavail" -gt 0 ]]; then
			sleep 1
			continue
		else
			echo "Deployment successful!"
			echo "New tag: $image_tag"
			break
		fi
	done

	k8s_describe

	exit "$final_exit_code"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment