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