Add healthcheck endpoints and scripts
This commit is contained in:
@ -0,0 +1,36 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
port, exists := os.LookupEnv("PORT")
|
||||||
|
if !exists {
|
||||||
|
port = "8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: 2 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Get("http://localhost:" + port + "/ping")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the HTTP Status Code and Status Name
|
||||||
|
fmt.Println("HTTP Response Status:", resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||||
|
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
|
||||||
|
fmt.Println("HTTP Status is in the 2xx range")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Argh! Broken")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,5 +42,11 @@ func main() {
|
|||||||
"now": tm,
|
"now": tm,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
r.Run() // listen and serve on 0.0.0.0:8080
|
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
tm = database.GetTime(c)
|
||||||
|
c.JSON(200, "pong")
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Run() // listen and serve on 0.0.0.0:8080 (or "PORT" env var)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
timeout: 2000,
|
||||||
|
host: 'localhost',
|
||||||
|
port: process.env.PORT || 3000,
|
||||||
|
path: '/ping',
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = http.request(options, (res) => {
|
||||||
|
console.info('STATUS: ' + res.statusCode);
|
||||||
|
process.exitCode = res.statusCode === 200 ? 0 : 1;
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on('error', function (err) {
|
||||||
|
console.error('ERROR', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.end();
|
||||||
@ -4,7 +4,7 @@ const express = require('express');
|
|||||||
const morgan = require('morgan');
|
const morgan = require('morgan');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = 3000;
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
// setup the logger
|
// setup the logger
|
||||||
app.use(morgan('tiny'));
|
app.use(morgan('tiny'));
|
||||||
@ -16,6 +16,10 @@ app.get('/', async (req, res) => {
|
|||||||
res.send(response);
|
res.send(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/ping', async (_, res) => {
|
||||||
|
res.send('pong');
|
||||||
|
});
|
||||||
|
|
||||||
const server = app.listen(port, () => {
|
const server = app.listen(port, () => {
|
||||||
console.log(`Example app listening on port ${port}`);
|
console.log(`Example app listening on port ${port}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
location /ping {
|
||||||
|
access_log off;
|
||||||
|
add_header 'Content-Type' 'text/plain';
|
||||||
|
return 200 "pong";
|
||||||
|
}
|
||||||
location /api/golang/ {
|
location /api/golang/ {
|
||||||
resolver 127.0.0.1;
|
resolver 127.0.0.1;
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
|||||||
58
06-building-container-images/api-golang/Dockerfile.7
Normal file
58
06-building-container-images/api-golang/Dockerfile.7
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Pin specific version for stability
|
||||||
|
# Use separate stage for building image
|
||||||
|
# Use debian for easier build utilities
|
||||||
|
FROM golang:1.19-bullseye AS build
|
||||||
|
|
||||||
|
# Add non root user
|
||||||
|
RUN useradd -u 1001 nonroot
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy only files required to install dependencies (better layer caching)
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
# Use cache mount to speed up install of existing dependencies
|
||||||
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Compile healthcheck
|
||||||
|
RUN go build \
|
||||||
|
-ldflags="-linkmode external -extldflags -static" \
|
||||||
|
-tags netgo \
|
||||||
|
-o healthcheck \
|
||||||
|
./healthcheck/healthcheck.go
|
||||||
|
|
||||||
|
# Compile application during build rather than at runtime
|
||||||
|
# Add flags to statically link binary
|
||||||
|
RUN go build \
|
||||||
|
-ldflags="-linkmode external -extldflags -static" \
|
||||||
|
-tags netgo \
|
||||||
|
-o api-golang
|
||||||
|
|
||||||
|
# Use separate stage for deployable image
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
# Set gin mode
|
||||||
|
ENV GIN_MODE=release
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
# Copy the passwd file
|
||||||
|
COPY --from=build /etc/passwd /etc/passwd
|
||||||
|
|
||||||
|
# Copy the healthcheck binary from the build stage
|
||||||
|
COPY --from=build /app/healthcheck/healthcheck healthcheck
|
||||||
|
|
||||||
|
# Copy the app binary from the build stage
|
||||||
|
COPY --from=build /app/api-golang api-golang
|
||||||
|
|
||||||
|
# Use nonroot user
|
||||||
|
USER nonroot
|
||||||
|
|
||||||
|
# Indicate expected port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["/api-golang"]
|
||||||
@ -11,7 +11,7 @@ build-N:
|
|||||||
|
|
||||||
.PHONY: build-all
|
.PHONY: build-all
|
||||||
build-all:
|
build-all:
|
||||||
for number in 0 1 2 3 4 5 6 ; do \
|
for number in 0 1 2 3 4 5 6 7; do \
|
||||||
N=$$number $(MAKE) build-N; \
|
N=$$number $(MAKE) build-N; \
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -21,6 +21,6 @@ push-N:
|
|||||||
|
|
||||||
.PHONY: push-all
|
.PHONY: push-all
|
||||||
push-all:
|
push-all:
|
||||||
for number in 0 1 2 3 4 5 6 ; do \
|
for number in 0 1 2 3 4 5 6 7; do \
|
||||||
N=$$number $(MAKE) push-N; \
|
N=$$number $(MAKE) push-N; \
|
||||||
done
|
done
|
||||||
|
|||||||
39
06-building-container-images/api-node/Dockerfile.8
Normal file
39
06-building-container-images/api-node/Dockerfile.8
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Pin specific version for stability
|
||||||
|
# Use alpine for reduced image size
|
||||||
|
FROM node:19.4-alpine
|
||||||
|
|
||||||
|
# Set NODE_ENV
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
# Specify working directory other than /
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy only files required to install
|
||||||
|
# dependencies (better layer caching)
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install only production dependencies
|
||||||
|
# Use cache mount to speed up install of existing dependencies
|
||||||
|
RUN --mount=type=cache,target=/usr/src/app/.npm \
|
||||||
|
npm set cache /usr/src/app/.npm && \
|
||||||
|
npm ci --only=production
|
||||||
|
|
||||||
|
# Use non-root user
|
||||||
|
# Use --chown on COPY commands to set file permissions
|
||||||
|
USER node
|
||||||
|
|
||||||
|
# Copy the healthcheck script
|
||||||
|
COPY --chown=node:node ./healthcheck/ .
|
||||||
|
|
||||||
|
# Copy remaining source code AFTER installing dependencies.
|
||||||
|
# Again, copy only the necessary files
|
||||||
|
COPY --chown=node:node ./src/ .
|
||||||
|
|
||||||
|
# Indicate expected port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD [ "node", "index.js" ]
|
||||||
|
|
||||||
|
# TODO: Use multi-stage with distroless image or chainguard image?
|
||||||
|
# https://github.com/GoogleContainerTools/distroless/blob/main/examples/nodejs/Dockerfile
|
||||||
|
# https://edu.chainguard.dev/chainguard/chainguard-images/reference/node/overview/
|
||||||
@ -11,7 +11,7 @@ build-N:
|
|||||||
|
|
||||||
.PHONY: build-all
|
.PHONY: build-all
|
||||||
build-all:
|
build-all:
|
||||||
for number in 0 1 2 3 4 5 6 7 ; do \
|
for number in 0 1 2 3 4 5 6 7 8; do \
|
||||||
N=$$number $(MAKE) build-N; \
|
N=$$number $(MAKE) build-N; \
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -21,6 +21,6 @@ push-N:
|
|||||||
|
|
||||||
.PHONY: push-all
|
.PHONY: push-all
|
||||||
push-all:
|
push-all:
|
||||||
for number in 0 1 2 3 4 5 6 7; do \
|
for number in 0 1 2 3 4 5 6 7 8; do \
|
||||||
N=$$number $(MAKE) push-N; \
|
N=$$number $(MAKE) push-N; \
|
||||||
done
|
done
|
||||||
@ -10,7 +10,7 @@ services:
|
|||||||
api-node:
|
api-node:
|
||||||
build:
|
build:
|
||||||
context: ../05-example-web-application/api-node/
|
context: ../05-example-web-application/api-node/
|
||||||
dockerfile: ../../06-building-container-images/api-node/Dockerfile.7
|
dockerfile: ../../06-building-container-images/api-node/Dockerfile.8
|
||||||
init: true
|
init: true
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
|||||||
@ -7,8 +7,10 @@ services:
|
|||||||
- 5173:5173
|
- 5173:5173
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ../05-example-web-application/client-react/src
|
source: ../05-example-web-application/client-react/
|
||||||
target: /usr/src/app/src
|
target: /usr/src/app/
|
||||||
|
- type: volume
|
||||||
|
target: /usr/src/app/node_modules
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ../08-running-containers/client-react/vite.config.js
|
source: ../08-running-containers/client-react/vite.config.js
|
||||||
target: /usr/src/app/vite.config.js
|
target: /usr/src/app/vite.config.js
|
||||||
@ -20,8 +22,10 @@ services:
|
|||||||
target: dev
|
target: dev
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ../05-example-web-application/api-node/src/
|
source: ../05-example-web-application/api-node/
|
||||||
target: /usr/src/app/src/
|
target: /usr/src/app/
|
||||||
|
- type: volume
|
||||||
|
target: /usr/src/app/node_modules
|
||||||
init: true
|
init: true
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
|||||||
@ -9,19 +9,21 @@ compose-up-d:
|
|||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
|
CIVO_SSH:="ssh://ubuntu@212.2.244.220"
|
||||||
|
|
||||||
.PHONY: swarm-init
|
.PHONY: swarm-init
|
||||||
swarm-init:
|
swarm-init:
|
||||||
docker swarm init
|
DOCKER_HOST=${CIVO_SSH} docker swarm init
|
||||||
|
|
||||||
.PHONY: swarm-deploy-stack
|
.PHONY: swarm-deploy-stack
|
||||||
swarm-deploy-stack:
|
swarm-deploy-stack:
|
||||||
docker stack deploy -c stack.yaml example-app
|
DOCKER_HOST=${CIVO_SSH} docker stack deploy -c docker-swarm.yml example-app
|
||||||
|
|
||||||
.PHONY: swarm-remove-stack
|
.PHONY: swarm-remove-stack
|
||||||
swarm-remove-stack:
|
swarm-remove-stack:
|
||||||
docker stack rm example-app
|
DOCKER_HOST=${CIVO_SSH} docker stack rm example-app
|
||||||
|
|
||||||
.PHONY: create-secrets
|
.PHONY: create-secrets
|
||||||
create-secrets:
|
create-secrets:
|
||||||
echo -n "foobarbaz" | docker secret create postgres-passwd -
|
echo -n "foobarbaz" | DOCKER_HOST=${CIVO_SSH} docker secret create postgres-passwd -
|
||||||
echo -n "postgres://postgres:foobarbaz@db:5432/postgres" | docker secret create database-url -
|
echo -n "postgres://postgres:foobarbaz@db:5432/postgres" | DOCKER_HOST=${CIVO_SSH} docker secret create database-url -
|
||||||
@ -7,8 +7,14 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost/ping"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
api-node:
|
api-node:
|
||||||
image: sidpalas/devops-directive-docker-course-api-node:7
|
image: sidpalas/devops-directive-docker-course-api-node:8
|
||||||
networks:
|
networks:
|
||||||
- frontend
|
- frontend
|
||||||
- backend
|
- backend
|
||||||
@ -18,8 +24,14 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- DATABASE_URL=postgres://postgres:foobarbaz@db:5432/postgres
|
- DATABASE_URL=postgres://postgres:foobarbaz@db:5432/postgres
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "src/healthcheck.js"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
api-golang:
|
api-golang:
|
||||||
image: sidpalas/devops-directive-docker-course-api-golang:6
|
image: sidpalas/devops-directive-docker-course-api-golang:7
|
||||||
networks:
|
networks:
|
||||||
- frontend
|
- frontend
|
||||||
- backend
|
- backend
|
||||||
@ -29,6 +41,12 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- DATABASE_URL=postgres://postgres:foobarbaz@db:5432/postgres
|
- DATABASE_URL=postgres://postgres:foobarbaz@db:5432/postgres
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "/healthcheck"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
db:
|
db:
|
||||||
image: postgres:15.1-alpine
|
image: postgres:15.1-alpine
|
||||||
networks:
|
networks:
|
||||||
@ -37,6 +55,11 @@ services:
|
|||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_PASSWORD=foobarbaz
|
- POSTGRES_PASSWORD=foobarbaz
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
|||||||
@ -7,8 +7,14 @@ services:
|
|||||||
- frontend
|
- frontend
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost/ping"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
api-node:
|
api-node:
|
||||||
image: sidpalas/devops-directive-docker-course-api-node:7
|
image: sidpalas/devops-directive-docker-course-api-node:8
|
||||||
environment:
|
environment:
|
||||||
- DATABASE_URL_FILE=/run/secrets/database-url
|
- DATABASE_URL_FILE=/run/secrets/database-url
|
||||||
secrets:
|
secrets:
|
||||||
@ -18,8 +24,14 @@ services:
|
|||||||
- backend
|
- backend
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "/usr/src/app/healthcheck.js"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
api-golang:
|
api-golang:
|
||||||
image: sidpalas/devops-directive-docker-course-api-golang:6
|
image: sidpalas/devops-directive-docker-course-api-golang:7
|
||||||
networks:
|
networks:
|
||||||
- frontend
|
- frontend
|
||||||
- backend
|
- backend
|
||||||
@ -29,6 +41,12 @@ services:
|
|||||||
- database-url
|
- database-url
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "/healthcheck"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
db:
|
db:
|
||||||
image: postgres:15.1-alpine
|
image: postgres:15.1-alpine
|
||||||
networks:
|
networks:
|
||||||
@ -41,6 +59,11 @@ services:
|
|||||||
- POSTGRES_PASSWORD_FILE=/run/secrets/postgres-passwd
|
- POSTGRES_PASSWORD_FILE=/run/secrets/postgres-passwd
|
||||||
secrets:
|
secrets:
|
||||||
- postgres-passwd
|
- postgres-passwd
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
|||||||
Reference in New Issue
Block a user