diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..30a68ee --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# PostgreSQL +DB_HOST=localhost +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=postgres +DB_NAME=hellodb + +# Backend +PORT=8080 + +# Frontend +NEXT_PUBLIC_API_URL=http://localhost:8080 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00b8087 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Dependencies +node_modules/ + +# Build +.next/ +out/ +dist/ + +# Environment +.env +.env.local +.env.*.local + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Go +backend/main +*.exe + +# Logs +*.log +npm-debug.log* diff --git a/.woodpecker.yaml b/.woodpecker.yaml new file mode 100644 index 0000000..b2eaab7 --- /dev/null +++ b/.woodpecker.yaml @@ -0,0 +1,78 @@ +# Woodpecker CI Pipeline +# Go Backend + Next.js Frontend + PostgreSQL + +when: + - event: [push, pull_request] + branch: main + +steps: + # 1. Backend Docker image build + build-backend: + image: docker:24-dind + privileged: true + commands: + - docker build -t hell-world-backend:${CI_COMMIT_SHA:0:8} ./backend + - docker tag hell-world-backend:${CI_COMMIT_SHA:0:8} registry.gecore.mn/library/hell-world-backend:${CI_COMMIT_SHA:0:8} + - docker tag hell-world-backend:${CI_COMMIT_SHA:0:8} registry.gecore.mn/library/hell-world-backend:latest + + # 2. Frontend Docker image build + build-frontend: + image: docker:24-dind + privileged: true + commands: + - docker build -t hell-world-frontend:${CI_COMMIT_SHA:0:8} ./frontend + - docker tag hell-world-frontend:${CI_COMMIT_SHA:0:8} registry.gecore.mn/library/hell-world-frontend:${CI_COMMIT_SHA:0:8} + - docker tag hell-world-frontend:${CI_COMMIT_SHA:0:8} registry.gecore.mn/library/hell-world-frontend:latest + + # 3. Push backend to Harbor registry + push-backend: + image: docker:24-dind + privileged: true + commands: + - echo "$HARBOR_PASSWORD" | docker login registry.gecore.mn -u "$HARBOR_USER" --password-stdin + - docker push registry.gecore.mn/library/hell-world-backend:${CI_COMMIT_SHA:0:8} + - docker push registry.gecore.mn/library/hell-world-backend:latest + secrets: [harbor_user, harbor_password] + when: + event: push + branch: main + + # 4. Push frontend to Harbor registry + push-frontend: + image: docker:24-dind + privileged: true + commands: + - echo "$HARBOR_PASSWORD" | docker login registry.gecore.mn -u "$HARBOR_USER" --password-stdin + - docker push registry.gecore.mn/library/hell-world-frontend:${CI_COMMIT_SHA:0:8} + - docker push registry.gecore.mn/library/hell-world-frontend:latest + secrets: [harbor_user, harbor_password] + when: + event: push + branch: main + + # 5. Update Kubernetes manifests with new image tags + update-manifests: + image: alpine:latest + commands: + - apk add --no-cache sed git + - sed -i "s|image:.*hell-world-backend.*|image: registry.gecore.mn/library/hell-world-backend:${CI_COMMIT_SHA:0:8}|g" manifests/backend-deployment.yaml + - sed -i "s|image:.*hell-world-frontend.*|image: registry.gecore.mn/library/hell-world-frontend:${CI_COMMIT_SHA:0:8}|g" manifests/frontend-deployment.yaml + - git config user.email "ci@gecore.mn" + - git config user.name "Woodpecker CI" + - git add manifests/ + - git commit -m "ci: update images to ${CI_COMMIT_SHA:0:8}" || true + - git push origin main || true + when: + event: push + branch: main + + # 6. Deploy notification + notify: + image: alpine:latest + commands: + - echo "✅ Build completed for commit ${CI_COMMIT_SHA:0:8}" + - echo "📦 Backend: registry.gecore.mn/library/hell-world-backend:${CI_COMMIT_SHA:0:8}" + - echo "📦 Frontend: registry.gecore.mn/library/hell-world-frontend:${CI_COMMIT_SHA:0:8}" + - echo "🔗 ArgoCD will sync automatically" + when: + status: [success, failure] diff --git a/README.md b/README.md index 79b75cf..3b2a557 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,126 @@ -# hell-world +# Hello World - Go + PostgreSQL + Next.js +Full-stack Hello World application using Go backend, PostgreSQL database, and Next.js frontend. + +## Project Structure + +``` +. +├── backend/ # Go API server +│ ├── main.go +│ ├── go.mod +│ └── Dockerfile +├── frontend/ # Next.js application +│ ├── app/ +│ ├── package.json +│ └── Dockerfile +├── docker-compose.yml +└── README.md +``` + +## Quick Start with Docker + +```bash +docker-compose up --build +``` + +Services: +- Frontend: http://localhost:3000 +- Backend API: http://localhost:8080 +- PostgreSQL: localhost:5432 + +## Manual Setup + +### Backend (Go) + +```bash +cd backend +go mod tidy +DB_HOST=localhost DB_PORT=5432 DB_USER=postgres DB_PASSWORD=postgres DB_NAME=hellodb go run main.go +``` + +### Frontend (Next.js) + +```bash +cd frontend +npm install +NEXT_PUBLIC_API_URL=http://localhost:8080 npm run dev +``` + +### PostgreSQL + +```bash +docker run -d \ + --name hello-postgres \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_DB=hellodb \ + -p 5432:5432 \ + postgres:16-alpine +``` + +## API Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/hello` | GET | Returns hello message and DB status | +| `/api/health` | GET | Health check | + +## Tech Stack + +- **Backend**: Go 1.21 +- **Database**: PostgreSQL 16 +- **Frontend**: Next.js 14, React 18, TypeScript + +## CI/CD Pipeline + +### Woodpecker CI + +`.woodpecker.yaml` файл нь дараах алхмуудыг гүйцэтгэнэ: + +1. **build-backend** - Go backend Docker image build +2. **build-frontend** - Next.js frontend Docker image build +3. **push-backend** - Harbor registry рүү backend push +4. **push-frontend** - Harbor registry рүү frontend push +5. **update-manifests** - Kubernetes manifest дахь image tag шинэчлэх +6. **notify** - Deployment notification + +### ArgoCD (GitOps) + +ArgoCD application тохируулах: + +```bash +kubectl apply -f argocd-application.yaml -n argocd +``` + +ArgoCD нь `manifests/` directory-г автоматаар sync хийнэ. + +## Kubernetes Deployment + +### Manifests + +``` +manifests/ +├── namespace.yaml # hell-world namespace +├── postgres-secret.yaml # Database credentials +├── postgres-pvc.yaml # PostgreSQL storage +├── postgres-deployment.yaml # PostgreSQL deployment +├── postgres-service.yaml # PostgreSQL service +├── backend-deployment.yaml # Go backend deployment +├── backend-service.yaml # Backend service +├── frontend-deployment.yaml # Next.js frontend deployment +├── frontend-service.yaml # Frontend service +├── ingress.yaml # Ingress with TLS +└── kustomization.yaml # Kustomize config +``` + +### Manual Deploy + +```bash +kubectl apply -k manifests/ +``` + +### URLs (Production) + +- Frontend: https://hell-world.gecore.mn +- Backend API: https://hell-world-api.gecore.mn diff --git a/argocd-application.yaml b/argocd-application.yaml new file mode 100644 index 0000000..a532950 --- /dev/null +++ b/argocd-application.yaml @@ -0,0 +1,35 @@ +# ArgoCD Application - GitOps deployment +# kubectl apply -f argocd-application.yaml -n argocd +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: hell-world + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + source: + # Gitea repository URL + repoURL: https://git.gecore.mn/admin/hell-world.git + targetRevision: HEAD + path: manifests + + destination: + server: https://kubernetes.default.svc + namespace: hell-world + + syncPolicy: + automated: + prune: true # Устгагдсан resource-уудыг автоматаар устгана + selfHeal: true # Гараар өөрчилсөн зүйлсийг автоматаар сэргээнэ + syncOptions: + - CreateNamespace=true + - PruneLast=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..af383cb --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +COPY go.mod ./ +RUN go mod download + +COPY . . +RUN go mod tidy && CGO_ENABLED=0 GOOS=linux go build -o main . + +FROM alpine:latest + +WORKDIR /app + +COPY --from=builder /app/main . + +EXPOSE 8080 + +CMD ["./main"] diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..9c8ffda --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,5 @@ +module hello-world-backend + +go 1.21 + +require github.com/lib/pq v1.10.9 diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 0000000..4263094 --- /dev/null +++ b/backend/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "database/sql" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + + _ "github.com/lib/pq" +) + +type Response struct { + Message string `json:"message"` + DBStatus string `json:"db_status"` +} + +var db *sql.DB + +func main() { + // PostgreSQL connection + dbHost := getEnv("DB_HOST", "localhost") + dbPort := getEnv("DB_PORT", "5432") + dbUser := getEnv("DB_USER", "postgres") + dbPassword := getEnv("DB_PASSWORD", "postgres") + dbName := getEnv("DB_NAME", "hellodb") + + connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + dbHost, dbPort, dbUser, dbPassword, dbName) + + var err error + db, err = sql.Open("postgres", connStr) + if err != nil { + log.Printf("Warning: Could not connect to database: %v", err) + } else { + defer db.Close() + } + + // HTTP handlers + http.HandleFunc("/api/hello", corsMiddleware(helloHandler)) + http.HandleFunc("/api/health", corsMiddleware(healthHandler)) + + port := getEnv("PORT", "8080") + log.Printf("Server starting on port %s", port) + log.Fatal(http.ListenAndServe(":"+port, nil)) +} + +func helloHandler(w http.ResponseWriter, r *http.Request) { + dbStatus := "disconnected" + + if db != nil { + err := db.Ping() + if err == nil { + dbStatus = "connected" + } + } + + response := Response{ + Message: "Hello World! - Go + PostgreSQL + Next.js", + DBStatus: dbStatus, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) +} + +func corsMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + + next(w, r) + } +} + +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2ecab8f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,46 @@ +services: + postgres: + image: postgres:16-alpine + container_name: hello-postgres + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: hellodb + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + + backend: + build: ./backend + container_name: hello-backend + ports: + - "8080:8080" + environment: + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: postgres + DB_PASSWORD: postgres + DB_NAME: hellodb + PORT: 8080 + depends_on: + postgres: + condition: service_healthy + + frontend: + build: ./frontend + container_name: hello-frontend + ports: + - "3000:3000" + environment: + NEXT_PUBLIC_API_URL: http://localhost:8080 + depends_on: + - backend + +volumes: + postgres_data: diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..5f4200a --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,23 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +COPY package*.json ./ +RUN npm install + +COPY . . +RUN npm run build + +FROM node:20-alpine AS runner + +WORKDIR /app + +ENV NODE_ENV=production + +COPY --from=builder /app/public ./public +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static + +EXPOSE 3000 + +CMD ["node", "server.js"] diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx new file mode 100644 index 0000000..818f168 --- /dev/null +++ b/frontend/app/layout.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Hello World App', + description: 'Go + PostgreSQL + Next.js', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx new file mode 100644 index 0000000..d314514 --- /dev/null +++ b/frontend/app/page.tsx @@ -0,0 +1,118 @@ +'use client' + +import { useEffect, useState } from 'react' + +interface ApiResponse { + message: string + db_status: string +} + +export default function Home() { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080' + + fetch(`${apiUrl}/api/hello`) + .then((res) => res.json()) + .then((data) => { + setData(data) + setLoading(false) + }) + .catch((err) => { + setError(err.message) + setLoading(false) + }) + }, []) + + return ( +
+
+

Hello World!

+

Go + PostgreSQL + Next.js

+ +
+ {loading &&

Loading...

} + {error &&

Error: {error}

} + {data && ( + <> +

{data.message}

+

+ Database: + + {data.db_status} + +

+ + )} +
+ +
+ Go + PostgreSQL + Next.js +
+
+
+ ) +} + +const styles: { [key: string]: React.CSSProperties } = { + main: { + minHeight: '100vh', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + fontFamily: 'system-ui, -apple-system, sans-serif', + }, + container: { + textAlign: 'center', + color: 'white', + }, + title: { + fontSize: '4rem', + marginBottom: '0.5rem', + textShadow: '2px 2px 4px rgba(0,0,0,0.2)', + }, + subtitle: { + fontSize: '1.25rem', + opacity: 0.9, + marginBottom: '2rem', + }, + card: { + background: 'rgba(255,255,255,0.95)', + borderRadius: '12px', + padding: '2rem', + color: '#333', + boxShadow: '0 10px 40px rgba(0,0,0,0.2)', + marginBottom: '2rem', + }, + message: { + fontSize: '1.25rem', + fontWeight: 500, + }, + status: { + marginTop: '1rem', + fontSize: '1rem', + }, + error: { + color: '#ef4444', + }, + techStack: { + display: 'flex', + gap: '1rem', + justifyContent: 'center', + }, + badge: { + background: 'rgba(255,255,255,0.2)', + padding: '0.5rem 1rem', + borderRadius: '20px', + fontSize: '0.875rem', + }, +} diff --git a/frontend/next.config.js b/frontend/next.config.js new file mode 100644 index 0000000..5cd8cc3 --- /dev/null +++ b/frontend/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'standalone', +} + +module.exports = nextConfig diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..c6dc3d3 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "hello-world-frontend", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "14.0.4", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "typescript": "^5.3.0" + } +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..e7ff90f --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/manifests/backend-deployment.yaml b/manifests/backend-deployment.yaml new file mode 100644 index 0000000..95f3973 --- /dev/null +++ b/manifests/backend-deployment.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend + namespace: hell-world + labels: + app: backend +spec: + replicas: 2 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + containers: + - name: backend + image: registry.gecore.mn/library/hell-world-backend:latest + ports: + - containerPort: 8080 + name: http + env: + - name: DB_HOST + value: postgres + - name: DB_PORT + value: "5432" + - name: DB_USER + valueFrom: + secretKeyRef: + name: postgres-secret + key: POSTGRES_USER + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: POSTGRES_PASSWORD + - name: DB_NAME + valueFrom: + secretKeyRef: + name: postgres-secret + key: POSTGRES_DB + - name: PORT + value: "8080" + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + livenessProbe: + httpGet: + path: /api/health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /api/health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 diff --git a/manifests/backend-service.yaml b/manifests/backend-service.yaml new file mode 100644 index 0000000..bbc2a6a --- /dev/null +++ b/manifests/backend-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: backend + namespace: hell-world + labels: + app: backend +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: backend diff --git a/manifests/frontend-deployment.yaml b/manifests/frontend-deployment.yaml new file mode 100644 index 0000000..9e9b845 --- /dev/null +++ b/manifests/frontend-deployment.yaml @@ -0,0 +1,45 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend + namespace: hell-world + labels: + app: frontend +spec: + replicas: 2 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - name: frontend + image: registry.gecore.mn/library/hell-world-frontend:latest + ports: + - containerPort: 3000 + name: http + env: + - name: NEXT_PUBLIC_API_URL + value: "https://hell-world-api.gecore.mn" + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "200m" + livenessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 diff --git a/manifests/frontend-service.yaml b/manifests/frontend-service.yaml new file mode 100644 index 0000000..7230599 --- /dev/null +++ b/manifests/frontend-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: frontend + namespace: hell-world + labels: + app: frontend +spec: + type: ClusterIP + ports: + - port: 3000 + targetPort: 3000 + protocol: TCP + name: http + selector: + app: frontend diff --git a/manifests/ingress.yaml b/manifests/ingress.yaml new file mode 100644 index 0000000..537cfce --- /dev/null +++ b/manifests/ingress.yaml @@ -0,0 +1,38 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: hell-world + namespace: hell-world + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + ingressClassName: nginx + tls: + - hosts: + - hell-world.gecore.mn + - hell-world-api.gecore.mn + secretName: hell-world-tls + rules: + # Frontend + - host: hell-world.gecore.mn + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: frontend + port: + number: 3000 + # Backend API + - host: hell-world-api.gecore.mn + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 diff --git a/manifests/kustomization.yaml b/manifests/kustomization.yaml new file mode 100644 index 0000000..4720df5 --- /dev/null +++ b/manifests/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: hell-world + +resources: + - namespace.yaml + - postgres-secret.yaml + - postgres-pvc.yaml + - postgres-deployment.yaml + - postgres-service.yaml + - backend-deployment.yaml + - backend-service.yaml + - frontend-deployment.yaml + - frontend-service.yaml + - ingress.yaml + +commonLabels: + app.kubernetes.io/name: hell-world + app.kubernetes.io/managed-by: kustomize diff --git a/manifests/namespace.yaml b/manifests/namespace.yaml new file mode 100644 index 0000000..4e31a78 --- /dev/null +++ b/manifests/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: hell-world + labels: + app: hell-world diff --git a/manifests/postgres-deployment.yaml b/manifests/postgres-deployment.yaml new file mode 100644 index 0000000..0dbe20e --- /dev/null +++ b/manifests/postgres-deployment.yaml @@ -0,0 +1,56 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + namespace: hell-world + labels: + app: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:16-alpine + ports: + - containerPort: 5432 + name: postgres + envFrom: + - secretRef: + name: postgres-secret + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "200m" + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + livenessProbe: + exec: + command: + - pg_isready + - -U + - postgres + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - pg_isready + - -U + - postgres + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-pvc diff --git a/manifests/postgres-pvc.yaml b/manifests/postgres-pvc.yaml new file mode 100644 index 0000000..a20384f --- /dev/null +++ b/manifests/postgres-pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc + namespace: hell-world +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: local-path diff --git a/manifests/postgres-secret.yaml b/manifests/postgres-secret.yaml new file mode 100644 index 0000000..9e89a26 --- /dev/null +++ b/manifests/postgres-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: postgres-secret + namespace: hell-world +type: Opaque +stringData: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: hellodb diff --git a/manifests/postgres-service.yaml b/manifests/postgres-service.yaml new file mode 100644 index 0000000..0c3e833 --- /dev/null +++ b/manifests/postgres-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: hell-world + labels: + app: postgres +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app: postgres