Initial commit: Schulungsunterlagen Tag 2 + Setup-Script
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
1ac9851e48
8 changed files with 332 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
tag3/
|
||||||
49
README.md
Normal file
49
README.md
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Container Training — Participant Materials
|
||||||
|
|
||||||
|
Welcome! This repository contains the code examples and listings for the 3-day container training.
|
||||||
|
|
||||||
|
## How to get this repo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.freemind.de/yberion/container-schulung.git
|
||||||
|
cd participants
|
||||||
|
```
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
listings/
|
||||||
|
visitcounter/
|
||||||
|
app/ ← Go application (Tag 2)
|
||||||
|
main.go
|
||||||
|
go.mod
|
||||||
|
Containerfile
|
||||||
|
docker-compose.yml
|
||||||
|
nginx/
|
||||||
|
default.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
## Day 2 — Building and Running the App
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd listings/visitcounter
|
||||||
|
|
||||||
|
# Build the image
|
||||||
|
podman build -t visitcounter:latest app/
|
||||||
|
|
||||||
|
# Start the full stack
|
||||||
|
podman compose up -d
|
||||||
|
|
||||||
|
# Check it's running
|
||||||
|
curl localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Podman installed (`podman --version`)
|
||||||
|
- podman-compose installed (`podman compose version`)
|
||||||
|
- Go is **not** required — it compiles inside the container
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
Ask your trainer.
|
||||||
18
listings/visitcounter/app/Containerfile
Normal file
18
listings/visitcounter/app/Containerfile
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# ── Stage 1: Kompilieren ─────────────────────────────────────────────────────
|
||||||
|
FROM golang:1.24-alpine AS builder
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
COPY main.go go.mod ./
|
||||||
|
# go mod tidy lädt Abhängigkeiten und erzeugt go.sum im Build-Layer
|
||||||
|
# CGO_ENABLED=0 → statisch gelinktes Binary, keine libc-Abhängigkeit
|
||||||
|
RUN go mod tidy \
|
||||||
|
&& CGO_ENABLED=0 GOOS=linux \
|
||||||
|
go build -ldflags="-s -w" -o visitcounter .
|
||||||
|
|
||||||
|
# ── Stage 2: Minimales Laufzeit-Image ────────────────────────────────────────
|
||||||
|
FROM scratch
|
||||||
|
# scratch = leeres Image ohne OS – das Binary läuft direkt auf dem Kernel
|
||||||
|
COPY --from=builder /build/visitcounter /visitcounter
|
||||||
|
EXPOSE 8080
|
||||||
|
USER 65534
|
||||||
|
ENTRYPOINT ["/visitcounter"]
|
||||||
5
listings/visitcounter/app/go.mod
Normal file
5
listings/visitcounter/app/go.mod
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
module visitcounter
|
||||||
|
|
||||||
|
go 1.24
|
||||||
|
|
||||||
|
require github.com/lib/pq v1.10.9
|
||||||
71
listings/visitcounter/app/main.go
Normal file
71
listings/visitcounter/app/main.go
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *sql.DB
|
||||||
|
|
||||||
|
func getenv(key, fallback string) string {
|
||||||
|
if v := os.Getenv(key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDB() {
|
||||||
|
dsn := fmt.Sprintf(
|
||||||
|
"host=%s dbname=%s user=%s password=%s sslmode=disable",
|
||||||
|
getenv("DB_HOST", "db"),
|
||||||
|
getenv("DB_NAME", "visitcount"),
|
||||||
|
getenv("DB_USER", "app"),
|
||||||
|
getenv("DB_PASSWORD", "secret"),
|
||||||
|
)
|
||||||
|
var err error
|
||||||
|
db, err = sql.Open("postgres", dsn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = db.Ping(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = db.Exec(
|
||||||
|
`CREATE TABLE IF NOT EXISTS visits (count INTEGER DEFAULT 0);` +
|
||||||
|
`INSERT INTO visits SELECT 0 WHERE NOT EXISTS (SELECT 1 FROM visits);`,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var count int
|
||||||
|
err := db.QueryRow("UPDATE visits SET count = count + 1 RETURNING count").Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "database error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "<h1>VisitCounter</h1><p>Visited <strong>%d</strong> times.</p>", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
http.Error(w, "db unreachable", http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
initDB()
|
||||||
|
http.HandleFunc("/", handleIndex)
|
||||||
|
http.HandleFunc("/health", handleHealth)
|
||||||
|
log.Println("listening on :8080")
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
48
listings/visitcounter/docker-compose.yml
Normal file
48
listings/visitcounter/docker-compose.yml
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: visitcount
|
||||||
|
POSTGRES_USER: app
|
||||||
|
POSTGRES_PASSWORD: secret
|
||||||
|
volumes:
|
||||||
|
- db-data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- app-net
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U app -d visitcount"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ./app
|
||||||
|
dockerfile: Containerfile
|
||||||
|
environment:
|
||||||
|
DB_HOST: db
|
||||||
|
DB_NAME: visitcount
|
||||||
|
DB_USER: app
|
||||||
|
DB_PASSWORD: secret
|
||||||
|
networks:
|
||||||
|
- app-net
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
image: nginx:1.27-alpine
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
networks:
|
||||||
|
- app-net
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-net:
|
||||||
9
listings/visitcounter/nginx/default.conf
Normal file
9
listings/visitcounter/nginx/default.conf
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://app:8080;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
131
setup/setup.sh
Executable file
131
setup/setup.sh
Executable file
|
|
@ -0,0 +1,131 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Container Training — Debian 12 Setup
|
||||||
|
# Als root oder mit sudo ausführen: sudo bash setup.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||||
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||||
|
fail() { echo -e "${RED}[FEHLER]${NC} $*"; exit 1; }
|
||||||
|
|
||||||
|
[ "$(id -u)" -eq 0 ] || fail "Bitte als root oder mit sudo ausführen."
|
||||||
|
|
||||||
|
echo "================================================"
|
||||||
|
echo " Container-Schulung — Setup (Debian 12)"
|
||||||
|
echo "================================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Trainingsuser ermitteln
|
||||||
|
TRAINING_USER=${SUDO_USER:-""}
|
||||||
|
if [ -z "$TRAINING_USER" ]; then
|
||||||
|
warn "SUDO_USER nicht gesetzt — Linger wird nicht konfiguriert."
|
||||||
|
warn "Ggf. manuell ausführen: loginctl enable-linger <username>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "[1/5] Basis-Tools..."
|
||||||
|
# -------------------------------------------------------
|
||||||
|
apt-get update -q
|
||||||
|
apt-get install -y \
|
||||||
|
iputils-ping \
|
||||||
|
iproute2 \
|
||||||
|
net-tools \
|
||||||
|
dnsutils \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
git \
|
||||||
|
vim \
|
||||||
|
jq \
|
||||||
|
bash-completion
|
||||||
|
ok "Netzwerk-Tools installiert."
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "[2/5] Podman-Abhängigkeiten (rootless)..."
|
||||||
|
# -------------------------------------------------------
|
||||||
|
apt-get install -y \
|
||||||
|
slirp4netns \
|
||||||
|
fuse-overlayfs \
|
||||||
|
uidmap \
|
||||||
|
dbus-user-session \
|
||||||
|
systemd-container
|
||||||
|
ok "Rootless-Abhängigkeiten installiert."
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "[3/5] Podman (Kubic-Repo — brauchen >= 4.4 für Quadlets)..."
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_12/ /' \
|
||||||
|
| tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
|
||||||
|
curl -fsSL https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/Debian_12/Release.key \
|
||||||
|
| gpg --dearmor \
|
||||||
|
| tee /etc/apt/trusted.gpg.d/devel_kubic_libcontainers_stable.gpg > /dev/null
|
||||||
|
apt-get update -q
|
||||||
|
apt-get install -y podman
|
||||||
|
|
||||||
|
PODMAN_VER=$(podman --version | awk '{print $3}')
|
||||||
|
PODMAN_MINOR=$(echo "$PODMAN_VER" | cut -d. -f2)
|
||||||
|
PODMAN_MAJOR=$(echo "$PODMAN_VER" | cut -d. -f1)
|
||||||
|
|
||||||
|
if [ "$PODMAN_MAJOR" -gt 4 ] || { [ "$PODMAN_MAJOR" -eq 4 ] && [ "$PODMAN_MINOR" -ge 4 ]; }; then
|
||||||
|
ok "Podman $PODMAN_VER — Quadlets werden unterstützt."
|
||||||
|
else
|
||||||
|
warn "Podman $PODMAN_VER — Quadlets brauchen >= 4.4! Tag 3 könnte Probleme machen."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "[4/5] podman-compose + trivy..."
|
||||||
|
# -------------------------------------------------------
|
||||||
|
apt-get install -y podman-compose 2>/dev/null \
|
||||||
|
|| pip3 install --quiet podman-compose
|
||||||
|
ok "podman-compose installiert."
|
||||||
|
|
||||||
|
# trivy
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
|
||||||
|
| sh -s -- -b /usr/local/bin latest
|
||||||
|
ok "trivy installiert: $(trivy --version | head -1)"
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "[5/5] Benutzer-Setup ($TRAINING_USER)..."
|
||||||
|
# -------------------------------------------------------
|
||||||
|
if [ -n "$TRAINING_USER" ]; then
|
||||||
|
loginctl enable-linger "$TRAINING_USER"
|
||||||
|
ok "Linger aktiviert für $TRAINING_USER — systemctl --user überlebt Logout."
|
||||||
|
|
||||||
|
# subuid/subgid für rootless Container
|
||||||
|
if ! grep -q "^${TRAINING_USER}:" /etc/subuid 2>/dev/null; then
|
||||||
|
usermod --add-subuids 100000-165535 "$TRAINING_USER"
|
||||||
|
usermod --add-subgids 100000-165535 "$TRAINING_USER"
|
||||||
|
ok "subuid/subgid konfiguriert."
|
||||||
|
else
|
||||||
|
ok "subuid/subgid bereits vorhanden."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Kein Trainingsuser — Linger und subuid/subgid übersprungen."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "================================================"
|
||||||
|
echo " Zusammenfassung"
|
||||||
|
echo "================================================"
|
||||||
|
podman --version
|
||||||
|
podman-compose --version 2>/dev/null | head -1 || true
|
||||||
|
trivy --version | head -1
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -n "$TRAINING_USER" ]; then
|
||||||
|
echo "Benutzer: $TRAINING_USER"
|
||||||
|
echo "Linger: $(loginctl show-user "$TRAINING_USER" --property=Linger --value 2>/dev/null || echo 'unbekannt')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
ok "Setup abgeschlossen. Bitte einmal aus- und wieder einloggen."
|
||||||
Loading…
Add table
Reference in a new issue