Skip to content
Open
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
34 changes: 34 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: kubedevnews
POSTGRES_USER: kubedevnews
POSTGRES_PASSWORD: Pg#123
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U kubedevnews -d kubedevnews"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

app:
build: ./src
ports:
- "8081:8080"
environment:
DB_HOST: db
DB_PORT: 5432
DB_DATABASE: kubedevnews
DB_USERNAME: kubedevnews
DB_PASSWORD: Pg#123
DB_SSL_REQUIRE: "false"
depends_on:
db:
condition: service_healthy
restart: unless-stopped

volumes:
pgdata:
6 changes: 6 additions & 0 deletions k8s/00-namespace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: kube-news
labels:
app.kubernetes.io/part-of: kube-news
13 changes: 13 additions & 0 deletions k8s/01-configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-news-config
namespace: kube-news
labels:
app.kubernetes.io/part-of: kube-news
data:
# Aponta para o Service do PostgreSQL (DNS interno do cluster)
DB_HOST: "postgres"
DB_PORT: "5432"
DB_DATABASE: "kubedevnews"
DB_SSL_REQUIRE: "false"
18 changes: 18 additions & 0 deletions k8s/02-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: v1
kind: Secret
metadata:
name: kube-news-secret
namespace: kube-news
labels:
app.kubernetes.io/part-of: kube-news
type: Opaque
# stringData permite escrever os valores em texto puro (o Kubernetes
# converte para base64 automaticamente). Em produção, considere usar
# um gerenciador de segredos (Sealed Secrets, External Secrets, Vault...).
stringData:
DB_USERNAME: "kubedevnews"
DB_PASSWORD: "Pg#123"
# Credenciais consumidas pelo container do Postgres
POSTGRES_DB: "kubedevnews"
POSTGRES_USER: "kubedevnews"
POSTGRES_PASSWORD: "Pg#123"
64 changes: 64 additions & 0 deletions k8s/03-postgres-statefulset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: kube-news
labels:
app.kubernetes.io/name: postgres
app.kubernetes.io/part-of: kube-news
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: postgres
template:
metadata:
labels:
app.kubernetes.io/name: postgres
app.kubernetes.io/part-of: kube-news
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- name: postgres
containerPort: 5432
envFrom:
- secretRef:
name: kube-news-secret
env:
# O Postgres grava os dados em /var/lib/postgresql/data; usar um
# subdiretório evita conflito com o lost+found do volume montado.
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command: ["pg_isready", "-U", "kubedevnews", "-d", "kubedevnews"]
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
livenessProbe:
exec:
command: ["pg_isready", "-U", "kubedevnews", "-d", "kubedevnews"]
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 5
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
17 changes: 17 additions & 0 deletions k8s/04-postgres-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: kube-news
labels:
app.kubernetes.io/name: postgres
app.kubernetes.io/part-of: kube-news
spec:
# Acesso apenas interno ao cluster (a app o consome via DNS "postgres")
type: ClusterIP
selector:
app.kubernetes.io/name: postgres
ports:
- name: postgres
port: 5432
targetPort: 5432
54 changes: 54 additions & 0 deletions k8s/05-app-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: kube-news
namespace: kube-news
labels:
app.kubernetes.io/name: kube-news
app.kubernetes.io/part-of: kube-news
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: kube-news
template:
metadata:
labels:
app.kubernetes.io/name: kube-news
app.kubernetes.io/part-of: kube-news
spec:
containers:
- name: kube-news
# >>> Substitua pela sua imagem publicada no registry <<<
image: marlonanderson/kube-news:1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
# Variáveis não sensíveis vêm do ConfigMap e as sensíveis do Secret
envFrom:
- configMapRef:
name: kube-news-config
- secretRef:
name: kube-news-secret
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 20
periodSeconds: 15
timeoutSeconds: 5
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
18 changes: 18 additions & 0 deletions k8s/06-app-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: v1
kind: Service
metadata:
name: kube-news
namespace: kube-news
labels:
app.kubernetes.io/name: kube-news
app.kubernetes.io/part-of: kube-news
spec:
# ClusterIP + Ingress é o padrão mais portável entre nuvens.
# Para expor diretamente via balanceador da nuvem, troque para LoadBalancer.
type: ClusterIP
selector:
app.kubernetes.io/name: kube-news
ports:
- name: http
port: 80
targetPort: http
25 changes: 25 additions & 0 deletions k8s/07-app-ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kube-news
namespace: kube-news
labels:
app.kubernetes.io/name: kube-news
app.kubernetes.io/part-of: kube-news
annotations:
# Ajuste conforme o seu controller (nginx, traefik, alb, gce...).
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
# >>> Troque pelo domínio que você usará na nuvem <<<
- host: kube-news.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kube-news
port:
number: 80
8 changes: 8 additions & 0 deletions src/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
npm-debug.log
.git
.gitignore
Dockerfile
.dockerignore
*.md
.env
28 changes: 28 additions & 0 deletions src/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# syntax=docker/dockerfile:1

# ---- Stage de dependências ----
FROM node:22-alpine AS deps
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# ---- Stage final ----
FROM node:22-alpine
ENV NODE_ENV=production
WORKDIR /usr/src/app

# Dependências já resolvidas
COPY --from=deps /usr/src/app/node_modules ./node_modules

# Código da aplicação (static/, views/, models/, etc.)
COPY . .

# Executa como usuário não-root
USER node

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD node -e "require('http').get('http://localhost:8080/health',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"

CMD ["node", "server.js"]