forked from zalando/postgres-operator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
run_operator_locally.sh
executable file
·319 lines (239 loc) · 10.3 KB
/
run_operator_locally.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#!/usr/bin/env bash
#
# Deploy a Postgres Operator to a minikube aka local Kubernetes cluster
# Optionally re-build the operator binary beforehand to test local changes
# Known limitations:
# 1) minikube provides a single node K8s cluster. That is, you will not be able test functions like pod
# migration between multiple nodes locally
# 2) this script configures the operator via configmap, not the operator CRD
# enable unofficial bash strict mode
set -o errexit
set -o nounset
set -o pipefail
IFS=$'\n\t'
readonly PATH_TO_LOCAL_OPERATOR_MANIFEST="/tmp/local-postgres-operator-manifest.yaml"
readonly PATH_TO_PORT_FORWARED_KUBECTL_PID="/tmp/kubectl-port-forward.pid"
readonly PATH_TO_THE_PG_CLUSTER_MANIFEST="/tmp/minimal-postgres-manifest.yaml"
readonly LOCAL_PORT="8080"
readonly OPERATOR_PORT="8080"
# minikube needs time to create resources,
# so the script retries actions until all the resources become available
function retry(){
local -r retry_cmd="$1"
local -r retry_msg="$2"
# Time out after three minutes.
for i in {1..60}; do
if eval "$retry_cmd"; then
return 0
fi
echo "$retry_msg"
sleep 3
done
>2& echo "The command $retry_cmd timed out"
return 1
}
function display_help(){
echo "Usage: $0 [ -r | --rebuild-operator ] [ -h | --help ] [ -n | --deploy-new-operator-image ] [ -t | --deploy-pg-to-namespace-test ]"
}
function clean_up(){
echo "==== CLEAN UP PREVIOUS RUN ==== "
local status
status=$(minikube status --format "{{.Host}}" || true)
if [[ "$status" = "Running" ]] || [[ "$status" = "Stopped" ]]; then
echo "Delete the existing local cluster so that we can cleanly apply resources from scratch..."
minikube delete
fi
if [[ -e "$PATH_TO_LOCAL_OPERATOR_MANIFEST" ]]; then
rm -v "$PATH_TO_LOCAL_OPERATOR_MANIFEST"
fi
# the kubectl process does the port-forwarding between operator and local ports
# we restart the process to bind to the same port again (see end of script)
if [[ -e "$PATH_TO_PORT_FORWARED_KUBECTL_PID" ]]; then
local pid
pid=$( < "$PATH_TO_PORT_FORWARED_KUBECTL_PID")
# the process dies if a minikube stops between two invocations of the script
if kill "$pid" > /dev/null 2>&1; then
echo "Kill the kubectl process responsible for port forwarding for minikube so that we can re-use the same ports for forwarding later..."
fi
rm -v "$PATH_TO_PORT_FORWARED_KUBECTL_PID"
fi
}
function start_minikube(){
echo "==== START MINIKUBE ===="
echo "May take a few minutes ..."
minikube start
kubectl config set-context minikube
echo "==== MINIKUBE STATUS ===="
minikube status
echo ""
}
function build_operator_binary(){
# redirecting stderr greatly reduces non-informative output during normal builds
echo "Build operator binary (stderr redirected to /dev/null)..."
make clean deps local test > /dev/null 2>&1
}
function deploy_self_built_image() {
echo "==== DEPLOY CUSTOM OPERATOR IMAGE ==== "
build_operator_binary
# the fastest way to run a docker image locally is to reuse the docker from minikube
# set docker env vars so that docker can talk to the Docker daemon inside the minikube
eval $(minikube docker-env)
# image tag consists of a git tag or a unique commit prefix
# and the "-dev" suffix if there are uncommited changes in the working dir
local -x TAG
TAG=$(git describe --tags --always --dirty="-dev")
readonly TAG
# build the image
make docker > /dev/null 2>&1
# update the tag in the postgres operator conf
# since the image with this tag already exists on the machine,
# docker should not attempt to fetch it from the registry due to imagePullPolicy
sed -e "s/\(image\:.*\:\).*$/\1$TAG/; s/smoke-tested-//" manifests/postgres-operator.yaml > "$PATH_TO_LOCAL_OPERATOR_MANIFEST"
retry "kubectl apply -f \"$PATH_TO_LOCAL_OPERATOR_MANIFEST\"" "attempt to create $PATH_TO_LOCAL_OPERATOR_MANIFEST resource"
}
function start_operator(){
echo "==== START OPERATOR ===="
echo "Certain operations may be retried multiple times..."
# the order of resource initialization is significant
local file
for file in "configmap.yaml" "operator-service-account-rbac.yaml"
do
retry "kubectl create -f manifests/\"$file\"" "attempt to create $file resource"
done
cp manifests/postgres-operator.yaml $PATH_TO_LOCAL_OPERATOR_MANIFEST
if [[ "$should_build_custom_operator" = true ]]; then # set in main()
deploy_self_built_image
else
retry "kubectl create -f ${PATH_TO_LOCAL_OPERATOR_MANIFEST}" "attempt to create ${PATH_TO_LOCAL_OPERATOR_MANIFEST} resource"
fi
local -r msg="Wait for the postgresql custom resource definition to register..."
local -r cmd="kubectl get crd | grep --quiet 'postgresqls.acid.zalan.do'"
retry "$cmd" "$msg "
}
function forward_ports(){
echo "==== FORWARD OPERATOR PORT $OPERATOR_PORT TO LOCAL PORT $LOCAL_PORT ===="
local operator_pod
operator_pod=$(kubectl get pod -l name=postgres-operator -o jsonpath={.items..metadata.name})
# Spawn `kubectl port-forward` in the background to keep current terminal
# responsive. Hide stdout because otherwise there is a note about each TCP
# connection. Do not hide stderr so port-forward setup errors can be
# debugged. Sometimes the port-forward setup fails because expected k8s
# state isn't achieved yet. Try to detect that case and then run the
# command again (in a finite loop).
for _attempt in {1..20}; do
# Delay between retry attempts. First attempt should already be
# delayed.
echo "soon: invoke kubectl port-forward command (attempt $_attempt)"
sleep 5
# With the --pod-running-timeout=4s argument the process is expected
# to terminate within about that time if the pod isn't ready yet.
kubectl port-forward --pod-running-timeout=4s "$operator_pod" "$LOCAL_PORT":"$OPERATOR_PORT" 1> /dev/null &
_kubectl_pid=$!
_pf_success=true
# A successful `kubectl port-forward` setup can pragmatically be
# detected with a time-based criterion: it is a long-running process if
# successfully set up. If it does not terminate within deadline then
# consider the setup successful. Overall, observe the process for
# roughly 7 seconds. If it terminates before that it's certainly an
# error. If it did not terminate within that time frame then consider
# setup successful.
for ib in {1..7}; do
sleep 1
# Portable and non-blocking test: is process still running?
if kill -s 0 -- "${_kubectl_pid}" >/dev/null 2>&1; then
echo "port-forward process is still running"
else
# port-forward process seems to have terminated, reap zombie
set +e
# `wait` is now expected to be non-blocking, and exits with the
# exit code of pid (first arg).
wait $_kubectl_pid
_kubectl_rc=$?
set -e
echo "port-forward process terminated with exit code ${_kubectl_rc}"
_pf_success=false
break
fi
done
if [ ${_pf_success} = true ]; then
echo "port-forward setup seems successful. leave retry loop."
break
fi
done
if [ "${_pf_success}" = false ]; then
echo "port-forward setup failed after retrying. exit."
exit 1
fi
echo "${_kubectl_pid}" > "$PATH_TO_PORT_FORWARED_KUBECTL_PID"
}
function check_health(){
echo "==== RUN HEALTH CHECK ==== "
local -r check_cmd="curl --location --silent --output /dev/null http://127.0.0.1:$LOCAL_PORT/clusters"
local -r check_msg="Wait for port forwarding to take effect"
echo "Command for checking: $check_cmd"
if retry "$check_cmd" "$check_msg"; then
echo "==== SUCCESS: OPERATOR IS RUNNING ==== "
echo "To stop it cleanly, run 'minikube delete'"
else
>2& echo "==== FAILURE: OPERATOR DID NOT START OR PORT FORWARDING DID NOT WORK"
exit 1
fi
}
function submit_postgresql_manifest(){
echo "==== SUBMIT MINIMAL POSTGRES MANIFEST ==== "
local namespace="default"
cp manifests/minimal-postgres-manifest.yaml $PATH_TO_THE_PG_CLUSTER_MANIFEST
if $should_deploy_pg_to_namespace_test; then
kubectl create namespace test
namespace="test"
sed --in-place 's/namespace: default/namespace: test/' $PATH_TO_THE_PG_CLUSTER_MANIFEST
fi
kubectl create -f $PATH_TO_THE_PG_CLUSTER_MANIFEST
echo "The operator will create the PG cluster with minimal manifest $PATH_TO_THE_PG_CLUSTER_MANIFEST in the ${namespace} namespace"
}
function main(){
if ! [[ $(basename "$PWD") == "postgres-operator" ]]; then
echo "Please execute the script only from the root directory of the Postgres Operator repo."
exit 1
fi
trap "echo 'If you observe issues with minikube VM not starting/not proceeding, consider deleting the .minikube dir and/or rebooting before re-running the script'" EXIT
local should_build_custom_operator=false
local should_deploy_pg_to_namespace_test=false
local should_replace_operator_image=false
while true
do
# if the 1st param is unset, use the empty string as a default value
case "${1:-}" in
-h | --help)
display_help
exit 0
;;
-r | --rebuild-operator) # with minikube restart
should_build_custom_operator=true
break
;;
-n | --deploy-new-operator-image) # without minikube restart that takes minutes
should_replace_operator_image=true
break
;;
-t | --deploy-pg-to-namespace-test) # to test multi-namespace support locally
should_deploy_pg_to_namespace_test=true
break
;;
*) break
;;
esac
done
if ${should_replace_operator_image}; then
deploy_self_built_image
exit 0
fi
clean_up
start_minikube
start_operator
submit_postgresql_manifest
forward_ports
check_health
exit 0
}
main "$@"