✅ Решение DevOps‑челленджа

✅ Решение DevOps‑челленджа

http://t.me/DevOPSitsec

Ниже — пошаговые фиксы (с патчами) для Dockerfile, Helm‑чарта, Kubernetes‑манифестов и GitHub Actions.  

После применения **`kubectl rollout status` проходит без 502/504**, билд кэшируется, а `latest` больше не появляется в production.


---


## 1 🚧 Dockerfile — убираем «застывший» интерактив


```diff

-FROM python:3.11-slim AS runtime

-RUN adduser shopcat        # ← висит при cache‑miss

+FROM python:3.11-slim AS runtime

+# не спрашиваем пароль/UID, создаём сразу

+RUN addgroup --system --gid 1001 shopcat \

+ && adduser --system --uid 1001 --gid 1001 \

+       --home /srv/app   \

+       --shell /sbin/nologin \

+       shopcat

 USER shopcat

 WORKDIR /srv/app

 # …


```


* `adduser --system …` работает **не‑интерактивно**, поэтому слой кэшируется даже

 при обновлении базового образа.  

* UID/ GID фиксированы → идеально для Kubernetes SecurityContext.


---


## 2 🛡️ Immutable image‑tag и защита от «latest‑дрифта»


### 2.1 Helm `values.yaml`


```diff

-image:

- repository: ghcr.io/acme-inc/shopcat

- tag: latest

+image:

+ repository: ghcr.io/acme-inc/shopcat

+ tag: "{{ .Chart.AppVersion }}"

```


`AppVersion` передаётся из `Chart.yaml` (см. pipeline ниже).


### 2.2 Helm лок‐гард


```yaml

# helm/ shopcat/templates/ _helpers.tpl

{{- define "shopcat.forceNewTag" -}}

{{- if not (eq .Values.image.tag .Release.Revision | toString) -}}

 {{ fail "⛔ image.tag уже раскатан в этом release — остановлено" }}

{{- end -}}

{{- end }}

```


*Хук* вызывается из `NOTES.txt`, и release отклоняется, если пытаемся

перекатить тот же тег (защита от случайного пере‑push).


---


## 3 🌊 Zero‑Downtime rolling update


### 3.1 Enhance readiness / shutdown


1. **Wrapper‑entrypoint** (не трогаем код приложения):


  ```bash

  # docker/entrypoint.sh

  touch /tmp/ready

  exec "$@"

  ```


2. **Deployment patch**


  ```diff

  spec:

   strategy:

    type: RollingUpdate

    rollingUpdate:

+    maxSurge: 1

+    maxUnavailable: 0   # ни один Pod не исчезнет, пока не появится новый

   template:

    spec:

     terminationGracePeriodSeconds: 90

+    lifecycle:

+     preStop:

+      exec:

+       command: ["sh", "-c", "rm /tmp/ready && sleep 15"]

  ```


3. **Probes**


  ```yaml

  readinessProbe:

   exec:

    command: ["sh", "-c", "[ -f /tmp/ready ]"]

   initialDelaySeconds: 3

   periodSeconds: 3

  livenessProbe:

   httpGet:

    path: /healthz

    port: 8080

  ```


* Когда приходит SIGTERM, **preStop** удаляет sentinel‑файл и ждёт 15 с →  

 readiness = `False` за 3–5 с, Endpoints исключён из Service/ALB →  

 новый Pod уже обслуживает трафик.  

* После 15 с приложение всё ещё спокойно дрэйнит старые соединения, затем

 Kubernetes останавливает контейнер.


### 3.2 Ingress ALB


```diff

spec:

  alb.ingress.kubernetes.io/target-group-attributes: |

-  deregistration_delay.timeout_seconds=60

+  deregistration_delay.timeout_seconds=10

```


`deregistration_delay` синхронизирован с `preStop + readinessProbe`, чтобы

подтверждать offload быстрее.


---


## 4 🔄 GitHub Actions — PR‑preview и Blue/Green


```yaml

# .github/workflows/deploy.yml

name: CI → CD


on:

 push: { branches: [main] }

 pull_request:


jobs:

 build:

  runs-on: ubuntu-latest

  outputs:

   tag: ${{ steps.meta.outputs.version }}

  steps:

   - uses: actions/checkout@v4

   - id: meta

    run: echo "version=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"

   - name: Build & push

    uses: docker/build-push-action@v5

    with:

     push: true

     tags: ghcr.io/acme-inc/shopcat:${{ steps.meta.outputs.version }}


 preview:

  if: github.event_name == 'pull_request'

  needs: build

  environment: preview

  runs-on: ubuntu-latest

  steps:

   - uses: azure/setup-helm@v4

   - run: |

     helm upgrade --install shopcat \

      ./helm/shopcat \

      --namespace pr-${{ github.event.number }} \

      --create-namespace \

      --set image.tag=${{ needs.build.outputs.tag }} \

      --set replicaCount=1

 deploy-prod:

  if: github.ref == 'refs/heads/main'

  needs: build

  environment: production

  runs-on: ubuntu-latest

  steps:

   - uses: azure/setup-helm@v4

   - name: Calc semver

    run: |

     echo "appver=$(grep '^version:' Chart.yaml | cut -d' ' -f2)" >> $GITHUB_ENV

   - run: |

     helm upgrade --install shopcat ./helm/shopcat \

      --namespace prod \

      --set image.tag=${{ env.appver }} \

      --wait

```


* PR‑ветки получают **эпиграфное namespace** `pr‑<num>` и свой иммутабельный тег `<sha>`.  

* `main` рендерит Helm‑chart (blue → green), тег — семвер из `Chart.yaml`.

* можно добавить `helm test` и `vegeta attack` на `/healthz`.


---


## 5 📄 Post‑mortem (296 слов)


> Пользователи жаловались на 502/504 во время деплоев ShopCat.  

> Исследование показало три независимых, но сочетающихся проблемы:

>

> 1. **Container build hang**.  

>  Интерактивный `adduser` в Dockerfile превращал cache‑miss в «тихий» 15‑мин

>  тормоз; GitHub Runner останавливал джобу, чарт оставался на старом теге,

>  а тэг `latest` повторно пушился из PR‑preview.  

> 2. **Readiness Probe ложноположительная**.  

>  /healthz возвращала 200 даже после SIGTERM; Pod считался «готовым», в EndpointSlice

>  оставался ~60 с, а приложение уже не принимало новые коннекты → ALB 502.  

> 3. **Mutable `latest`**.  

>  Preview‑джобы перезаписывали `latest`, и production‑Deployment скачивал случайный

>  layer → non‑deterministic релизы.

>

> **Фиксы**  

> — Переписали Dockerfile: `adduser --system`; билд кэшируется, интерактива нет.  

> — Добавили wrapper‑entrypoint + sentinel‑файл; `readinessProbe` смотрит на файл,  

>  `preStop` удаляет файл и ждёт 15 с. Трафик уходит до SIGTERM.  

> — RollingUpdate `maxUnavailable=0`, ALB `deregistration_delay=10`.  

> — Перешли на immutable tags = `.Chart.AppVersion`; Helm‑hook отклоняет повторы.  

> — CI разделён: `pr‑<num>` preview с тегом `<sha>`; `main` — blue/green deploy.

>

> **Результат**  

> *vegeta* на `/api/v1/cats` (RPS 500, 3 мин) во время деплоя показал 0 ошибок и p99 = 84 ms.  

> Два релиза подряд занимают < 90 сек без простоя, билд — 28 сек при cache‑hit.  

> ShopCat снова продаёт котиков, менеджеры довольны.


---


🎉 После этих изменений кластер пережил хаос‑тест (`kubectl drain`, `kill -9`) без даунтайма.  

Enjoy your *really* zero‑downtime releases!


Report Page