-
Notifications
You must be signed in to change notification settings - Fork 3
284 lines (246 loc) · 11.2 KB
/
test-k8s-service-stream.yml
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
name: 'K8S: Stream Service'
on:
# This workflow is also part of the release pipeline,
# in that case, the actual version is deployed twice.
workflow_call:
# In PR the previous version is deployed first and then the actual version.
pull_request:
paths:
- .github/workflows/test-k8s-service-stream.yml
- provisioning/stream/**
- provisioning/common/**
# When the config structure is changed it may be necessary to adjust k8s configmap.
- internal/pkg/service/stream/config/**
env:
MINIKUBE_PROFILE: stream
MINIKUBE_DRIVER: docker
KUBERNETES_NAMESPACE: stream
KUBERNETES_ROLLOUT_WAIT: 200s
REMOVE_RESOURCES_LIMITS: true
SERVICE_NAME: Stream
ETCD_RELEASE_NAME: stream-etcd
ETCD_ENDPOINT: stream-etcd-headless.stream.svc.cluster.local:2379
METRICS_PORT: 9000
defaults:
run:
working-directory: provisioning/stream
jobs:
test:
name: "K8S test: Stream Service"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
android: true
dotnet: true
haskell: true
large-packages: false
docker-images: false
swap-storage: false
- name: Create artifacts directory
run: mkdir -p /tmp/artifacts
- name: Copy latest scripts to a temp dir
run: cp -R ${{ github.workspace }}/provisioning/common/scripts /tmp/latest-scripts
- name: Install gron tool
run: |
url="https://github.com/tomnomnom/gron/releases/download/v0.7.1/gron-linux-amd64-0.7.1.tgz"
curl -L "$url" | tar -xz -C /usr/local/bin
- name: Install MiniKube
run: /tmp/latest-scripts/minikube/install.sh
- name: Start MiniKube
run: /tmp/latest-scripts/minikube/start.sh
- name: Set Kubernetes namespace
run: kubectl config set-context --current "--namespace=$KUBERNETES_NAMESPACE"
- name: Checkout BASE branch (or HEAD if it is not a pull request)
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
- name: Deploy the old version, from the BASE branch
continue-on-error: true
run: ./deploy_local.sh
- name: Dump the old version (for diff)
continue-on-error: true
run: sleep 10 && /tmp/latest-scripts/k8s/dump.sh /tmp/artifacts/test-k8s-state.old.json
- name: Checkout HEAD branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Deploy the new version, from the HEAD branch
run: ./deploy_local.sh
- name: Dump the new version (for diff)
if: always()
run: |
set -Eeuo pipefail
# Delete empty old replica sets
emptyReplicaSets=$(kubectl get replicasets --ignore-not-found | tail --lines=+2 | awk '{if ($2 + $3 + $4 == 0) print $1}')
echo -e "Found empty replica sets:"
echo "$emptyReplicaSets"
echo "-------------------------"
echo "$emptyReplicaSets" | xargs --no-run-if-empty -I {} kubectl delete replicaset "{}"
# Wait for pod startup/termination
sleep 10
# Dump objects
/tmp/latest-scripts/k8s/dump.sh /tmp/artifacts/test-k8s-state.new.json
- name: Check deployment of the etcd
if: always()
run: kubectl rollout status "sts/$ETCD_RELEASE_NAME" --timeout=10s
- name: Check deployment of the API nodes
if: always()
run: kubectl rollout status "deployment/stream-api" --timeout=10s
- name: Check deployment of the HTTP source nodes
if: always()
run: kubectl rollout status "deployment/stream-http-source" --timeout=10s
- name: Check deployment of the storage writer/reader nodes
if: always()
run: kubectl rollout status "sts/stream-storage-writer-reader" --timeout=10s
- name: Check deployment of the storage coordinator nodes
if: always()
run: kubectl rollout status "deployment/stream-storage-coordinator" --timeout=10s
- name: Check access to the metrics from the DataDog Agent
if: always()
run: |
set -Eeuo pipefail
kubectl create namespace datadog || true
for APP_WITH_METRICS_PORT in "stream-api:9000" "stream-http-source:9000" "stream-storage-writer-reader:9001" "stream-storage-writer-reader:9002" "stream-storage-coordinator:9000"
do
IFS=: read -r APP METRICS_PORT <<< "$APP_WITH_METRICS_PORT"
echo "---------------------------------"
echo "Checking $APP:$METRICS_PORT"
export POD_IP=`kubectl get pod -l "app=$APP" -o=jsonpath='{.items[0].status.podIP}'`
echo "Pod IP: $POD_IP"
kubectl run --attach --rm --restart=Never check-api-datadog \
--namespace datadog \
--image docker.io/alpine/curl \
--labels="app=datadog-agent" \
--env="POD_IP=$POD_IP" \
--env="METRICS_PORT=$METRICS_PORT" \
--command -- sh -c "set -eo pipefail; curl -f -L --max-time 5 "$POD_IP:$METRICS_PORT/metrics" | tail"
echo "---------------------------------"
done
- name: Check forbidden access to the metrics from other places
if: always()
run: |
set -Eeuo pipefail
for APP_WITH_METRICS_PORT in "stream-api:9000" "stream-http-source:9000" "stream-storage-writer-reader:9001" "stream-storage-writer-reader:9002" "stream-storage-coordinator:9000"
do
IFS=: read -r APP METRICS_PORT <<< "$APP_WITH_METRICS_PORT"
echo "---------------------------------"
echo "Checking $APP:$METRICS_PORT"
export POD_IP=`kubectl get pod -l "app=$APP" -o=jsonpath='{.items[0].status.podIP}'`
echo "Pod IP: $POD_IP"
if kubectl run --attach --rm --restart=Never check-api-other \
--image docker.io/alpine/curl \
--env="POD_IP=$POD_IP" \
--env="METRICS_PORT=$METRICS_PORT" \
--command -- sh -c "set -eo pipefail; curl -f -L --max-time 5 "$POD_IP:$METRICS_PORT/metrics" | tail"; then
echo "The command did not fail, but it should have."
exit 1
else
echo "The command failed, OK."
exit 0
fi
echo "---------------------------------"
done
- name: Check access to the etcd from a client
if: always()
run: |
set -Eeuo pipefail
export ETCD_ROOT_PASSWORD=$(kubectl get secret "$ETCD_RELEASE_NAME" -o jsonpath="{.data.etcd-root-password}" 2>/dev/null | base64 -d)
kubectl run --attach --rm --restart=Never check-etcd-client \
--image docker.io/bitnami/etcd \
--labels="${ETCD_RELEASE_NAME}-client=true" \
--env="ETCD_ROOT_PASSWORD=$ETCD_ROOT_PASSWORD" \
--env="ETCDCTL_ENDPOINTS=$ETCD_ENDPOINT" \
--command -- etcdctl --dial-timeout=10s --user root:$ETCD_ROOT_PASSWORD put /message Hello
- name: Check access to the etcd from the DataDog Agent
if: always()
run: |
set -Eeuo pipefail
kubectl create namespace datadog || true
kubectl run --attach --rm --restart=Never check-etcd-other-datadog \
--namespace datadog \
--image docker.io/alpine/curl \
--labels="app=datadog-agent" \
--env="ETCD_ENDPOINT=$ETCD_ENDPOINT" \
--command -- sh -c "set -eo pipefail; curl -f -L --max-time 5 "$ETCD_ENDPOINT/metrics" | tail"
- name: Check forbidden access to the etcd from other places
if: always()
run: |
set -Eeuo pipefail
if kubectl run --attach --rm --restart=Never check-etcd-other \
--image docker.io/alpine/curl \
--env="ETCD_ENDPOINT=$ETCD_ENDPOINT" \
--command -- sh -c "set -eo pipefail; curl -f -L --max-time 5 "$ETCD_ENDPOINT/metrics" | tail"; then
echo "The command did not fail, but it should have."
exit 1
else
echo "The command failed, OK."
exit 0
fi
- name: Check API response
if: always()
run: |
hostPort=$(minikube service --url stream-api --namespace $KUBERNETES_NAMESPACE)
echo "$hostPort"
curl --fail -s --max-time 5 --retry 5 --retry-connrefused "$hostPort/health-check"
- name: Check HTTP source response
if: always()
run: |
hostPort=$(minikube service --url stream-http-source --namespace $KUBERNETES_NAMESPACE)
curl --fail -s --max-time 5 --retry 5 --retry-connrefused "$hostPort/health-check"
- name: Test etcd defragmentation cron job
if: always()
run: |
set -Eeuo pipefail
# Create a job from the cron job
kubectl create job "--from=cronjob/${ETCD_RELEASE_NAME}-defrag" test-defrag-job
# Wait for the job
if kubectl wait --for=condition=complete --timeout=30s job/test-defrag-job; then
echo "The job succeed, logs:"
kubectl logs --selector="job-name=test-defrag-job"
exit 0
else
echo "The job failed, logs:"
kubectl logs --selector="job-name=test-defrag-job"
exit 1
fi
- name: Diff the old and the new Kubernetes state
if: always()
run: |
set -Eeuo pipefail
# Diff JSON states
/tmp/latest-scripts/k8s/diff.sh \
/tmp/artifacts/test-k8s-state.old.json \
/tmp/artifacts/test-k8s-state.new.json \
/tmp/artifacts/test-k8s-state.diff
# Remove ANSI sequences
sed -e 's/\x1b\[[0-9;]*m//g' -i /tmp/artifacts/test-k8s-state.diff || true
# Prepare PR comment message
echo -e "### ${{ env.SERVICE_NAME }} Kubernetes Diff [CI]\n\n" >> /tmp/artifacts/test-k8s-state.diff.message
echo -e "Between \`base\` ${{ github.event.pull_request.base.sha }} :arrow_left: \`head\` ${{ github.event.pull_request.head.sha }}.\n\n" >> /tmp/artifacts/test-k8s-state.diff.message
echo -e "<details>\n<summary>Expand</summary>\n\n\`\`\`diff\n" >> /tmp/artifacts/test-k8s-state.diff.message
head -c 50000 /tmp/artifacts/test-k8s-state.diff >> /tmp/artifacts/test-k8s-state.diff.message
echo -e "\n\n(see artifacts in the Github Action for more information)\n\`\`\`\n</details>" >> /tmp/artifacts/test-k8s-state.diff.message
- name: Dump logs
if: always()
run: |
/tmp/latest-scripts/minikube/logs.sh /tmp/artifacts &&
/tmp/latest-scripts/k8s/logs.sh /tmp/artifacts
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: test-k8s-state-stream
path: /tmp/artifacts
if-no-files-found: error
- name: Send PR comment
uses: marocchino/sticky-pull-request-comment@v2
with:
header: "${{ env.KUBERNETES_NAMESPACE }}-kubernetes-state-diff"
recreate: true
path: /tmp/artifacts/test-k8s-state.diff.message