Skip to content
Open

lab6 #1215

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM golang:1.24-alpine AS builder
WORKDIR /build
COPY app/go.mod ./
RUN go mod download
COPY app/ .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -trimpath \
-o /build/quicknotes .

# Создаём папку /data и даём права в builder
RUN mkdir -p /data && chown 65532:65532 /data

FROM gcr.io/distroless/static:nonroot
WORKDIR /app
COPY --from=builder /build/quicknotes .
COPY --from=builder /data /data
COPY app/seed.json .
EXPOSE 8080
ENTRYPOINT ["/app/quicknotes"]
Empty file added [builder
Empty file.
Empty file added [stage-1
Empty file.
35 changes: 35 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
services:
quicknotes:
image: quicknotes:lab6
container_name: quicknotes-lab6
restart: unless-stopped
user: "0:0"
ports:
- "8080:8080"
volumes:
- quicknotes-data:/data
environment:
- ADDR=:8080
- DATA_PATH=/data/notes.json
- SEED_PATH=/app/seed.json
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 5s

# Drop all capabilities
cap_drop:
- ALL
# добавлять ничего не надо, так как приложению не нужны capabilities

# Read-only root filesystem
read_only: true

# no-new-privileges
security_opt:
- no-new-privileges:true

volumes:
quicknotes-data:
Empty file added default
Empty file.
Binary file added submissions/image-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-11.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-12.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-14.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-15.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-17.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-18.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-19.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-20.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-21.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image-9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added submissions/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
214 changes: 214 additions & 0 deletions submissions/lab6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# Lab 6

Frolova AI, M25RO-01

a.frolova@innopolis.university

Ссылка на PR: https://github.com/inno-devops-labs/DevOps-Intro/pull/1215

## Task1 - Dockerfile

Ссылка на Dockerfile: (https://github.com/kicchhi/DevOps-Intro/blob/feature/lab6/Dockerfile)

Размер Docker контейнера (размер < 25 MB):

![alt text](image.png)

Запуск контейнера:

![alt text](image-1.png)

Информация об образе (`docker inspect quicknotes:lab6 | jq '.[0].Config'`):

![alt text](image-2.png)

Размер образа `golang:1.24-alpine` - **83.5 MB**. В сравнении с ним, мой `quicknotes:lab6` весит 14.8 MB.

Это наглядно демонстрирует эффективность многостадийной сборки: мы исключаем из финального образа компилятор, пакетный менеджер и все инструменты сборки, оставляя только статический бинарник.

![alt text](image-3.png)

### Ответы на вопросы

> Q: Почему важен порядок слоев? Покажите время сборки до и после для двух стратегий:
>> 1) COPY . . && go mod download && go build - копируются все файлы проекта, включая исходники, слой .COPY инвалидируется, и go mod download перезапускается, даже если зависимости не менялись.
>> 2) COPY go.mod go.sum ./ && go mod download && COPY . . && go build - копируются только те файлы, в котоорых есть зависимости (go.mod и go.sum). Они меняются редко, поэтому слой с go mod download кещируется. Затем копируются исходники, и сборка идет быстро, даже если код часто меняется.

> A: Разница между двумя стратегиями составила 0.27 секунд. Для маленького проекта с маленьким количеством заависимостей эта раззнице почти незаметна, но на больших проектам может достигать минут.

1) Время сборки с плохой стратегией:

![alt text](image-4.png)

2) Время сборки с хорошей стратегией:

![alt text](image-5.png)

---

> Q: Почему CGO_ENABLED=0? Что произойдет в distroless-static, если вы забудете об этом?

> A: `CGO_ENABLED=0` отключает CGO и заставляет Go компилировать статически скомпонованный бинарник, который не требует внешних библиотек. Образ `gcr.io/distroless/static:nonroot` не содержит динамического компоновщика и системных библиотек. Если оставить `CGO_ENABLED=1`, Go соберёт динамически скомпонованный бинарник, который при запуске в distroless выдаст ошибку. Контейнер не запустится.

---

> Q: Что такое gcr.io/distroless/static:nonroot? Что в нем есть, чего нет и почему это важно для CVE?

> A: `gcr.io/distroless/static:nonroot` — это минимальный образ от Google, предназначенный для запуска статически скомпилированных приложений.

Что в нем есть:
- Минимальная файловая система;
- `ca-certificates` для http запросов;
- базовые файлы.

Чего нет:
- Оболочки (bash. sh);
- Пакетного менеджера;
- Утилит curl, grep, sed;
- Компилятора и инструментов разработки.

Почему это важно для CVE - отсутствие оболочки пакетного менеджеоа делает distroless-образы гораздо безопаснее. Там нет apt чтобы установить вредоносный пакет, bash для выполнения атаки и тд.

---

> Q: -ldflags='-s -w' и -trimpath: что делает каждый флаг и какова его стоимость?

> A: Эти флаги используются при сборке бинарного файла для уменьшения его размера и повышения производимости.

| Флаг | Что делает | Эффект на образ | Стоимость |
|------|------------|-----------------|-------------------|
| `-s` | Удаляет таблицу символов (имена функций, переменных) | Уменьшает размер бинарника | Усложняет отладку (нет имён функций в стеке ошибок) |
| `-w` | Удаляет DWARF-отладочную информацию | Уменьшает размер бинарника | Нельзя использовать отладчик (например, `dlv`) |
| `-trimpath` | Убирает абсолютные пути к исходным файлам | Делает бинарник воспроизводимым (не зависит от папки сборки) | Усложняет чтение трейсов (пути становятся относительными) |

---

## Task 2 - Compose + Healthcheck + Persistent Volume

### 2.1 compose.yaml

Ссылка на файл compose.yaml: https://github.com/kicchhi/DevOps-Intro/blob/feature/lab6/compose.yaml

### 2.2 Ответы на вопросы

> Q: В Distroless нет оболочки. Как вы проверяете его работоспособность? Выберите стратегию и объясните. (Варианты: HTTP через отдельный sidecar; wget-только отладочный образ; полагайтесь на стандартное поведение Docker, которое просто проверяет, жив ли процесс; используйте бинарный файл, который уже есть в образе.)

> A: Я оставила healthcheck с wget, но он не работает (unhealthy), потому что в distroless нет wget. В реальном продакшене я бы использовала debug-образ или sidecar, чтобы иметь корректный healthcheck.

---

> Q: Почему volumes: [quicknotes-data:/data] выживает docker compose down? И что все-таки его уничтожает?

> A: A: `docker compose down` останавливает и удаляет контейнеры, но не удаляет именованные тома. Том сохраняется на диске, пока его не удалить командой `docker compose down -v` или `docker volume rm quicknotes-data`.

---

> Q: depends_on без condition: service_healthy — чего он на самом деле ждет? К каким ошибкам это может привести?

> A: `depends_on` по умолчанию ждёт старта контейнера, а не его готовности к работе. Это может привести к ошибкам, если зависимый сервис ещё не готов (например, база данных ещё не поднялась). Для корректной работы нужно использовать `condition: service_healthy`.

---

### 2.3 Persistence test

> Последовательность: note present → down → up → present → down -v → up → absent

**Проблема:**
При запуске контейнера от `nonroot` возникает ошибка `permission denied` при записи в монтированный том `/data`.

**Решение:**
В `compose.yaml` добавлена строка `user: "0:0"`, которая запускает контейнер от root. Это позволяет записывать данные в том и корректно выполнять тест на устойчивость.


1. Запускаю контейнер:

![alt text](image-6.png)

2. Создаю заметку:

![alt text](image-7.png)

3. Проверяю, что заметка создалась:

![alt text](image-8.png)

4. Останавливаю compose без удаления тома (`docker compose down`):

![alt text](image-9.png)

5. Запускаю compose снова:

![alt text](image-10.png)

6. Проверяю, что заметка созранилась:

![alt text](image-11.png)

7. Останавливаю compose с удалением тома (`docker compose down -v`):

![alt text](image-12.png)

8. Запускаю:

![alt text](image-13.png)

9. После остановки с удалением тома заметка ожидаемо не сохранилась:

![alt text](image-14.png)

---

## Bonus

- На этом этапе дополнила compose.yaml (добавила security_opt и read_only)

1. Проверка USER nonroot - 65532 - UID пользователя nonroot.

`docker inspect quicknotes:lab6 --format='{{.Config.User}}'`

![alt text](image-15.png)

2. Проверка Distroless - означает, что в образе нетт оболочки

`docker compose exec quicknotes sh`

![alt text](image-16.png)

3. Проверка cap_drop: ALL - все избыточные привилегии отключены. Контейнер имеет только минимально необходимые права.

`docker inspect quicknotes-lab6 --format='{{.HostConfig.CapDrop}}'`

![alt text](image-17.png)

4. Проверка read_only true - файловая система корня доступна только для чтения, а утилита touch отсутствует, что предотвращает запись в системные директории.

`docker compose exec quicknotes touch /test.txt`

![alt text](image-18.png)

5. no-new-privilegies - процесс внутри контейнера не может получить новые привилегии, что предотвращает эскалацию прав.

`docker inspect quicknotes-lab6 --format='{{.HostConfig.SecurityOpt}}'`

![alt text](image-19.png)

6. Trivy scan

- Базовый образ (`debian 13.5`): **0 HIGH/CRITICAL** уязвимостей
- Go-бинарник (`gobinary`): **13 HIGH** уязвимостей (в стандартной библиотеке Go)

Уязвимости связаны с версией Go 1.24.13, а не с кодом QuickNotes. Для устранения достаточно обновить базовый образ до `golang:1.24-alpine` с актуальными патчами. Это демонстрирует важность регулярного обновления базовых образов.

```bash
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:0.59.1 image --severity HIGH,CRITICAL --no-progress \
quicknotes:lab6
```

![alt text](image-20.png)

![alt text](image-21.png)

### Какой из 6 defaults даёт наибольшую безопасность на строку YAML?

`cap_drop: ALL` — одной строкой отключает все избыточные привилегии Linux, которые есть у контейнера по умолчанию. Это самая эффективная строчка для безопасности, потому что она предотвращает потенциальную эскалацию прав в случае взлома приложения, не требуя изменения кода или образа. В сочетании с `read_only: true` и `no-new-privileges` она образует минимальный набор hardening-правил для production-контейнеров.