Add healthcheck endpoints and scripts

This commit is contained in:
sid palas
2023-02-05 10:16:47 -05:00
parent af4bca05d0
commit 1ea6754c7f
14 changed files with 241 additions and 20 deletions

View File

@ -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)
}
}

View File

@ -42,5 +42,11 @@ func main() {
"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)
}

View File

@ -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();

View File

@ -4,7 +4,7 @@ const express = require('express');
const morgan = require('morgan');
const app = express();
const port = 3000;
const port = process.env.PORT || 3000;
// setup the logger
app.use(morgan('tiny'));
@ -16,6 +16,10 @@ app.get('/', async (req, res) => {
res.send(response);
});
app.get('/ping', async (_, res) => {
res.send('pong');
});
const server = app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});

View File

@ -1,5 +1,10 @@
server {
listen 80;
location /ping {
access_log off;
add_header 'Content-Type' 'text/plain';
return 200 "pong";
}
location /api/golang/ {
resolver 127.0.0.1;
proxy_set_header X-Forwarded-Host $host;

View 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"]

View File

@ -11,7 +11,7 @@ build-N:
.PHONY: 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; \
done
@ -21,6 +21,6 @@ push-N:
.PHONY: 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; \
done

View 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/

View File

@ -11,7 +11,7 @@ build-N:
.PHONY: 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; \
done
@ -21,6 +21,6 @@ push-N:
.PHONY: 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; \
done

View File

@ -10,7 +10,7 @@ services:
api-node:
build:
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
depends_on:
- db

View File

@ -7,8 +7,10 @@ services:
- 5173:5173
volumes:
- type: bind
source: ../05-example-web-application/client-react/src
target: /usr/src/app/src
source: ../05-example-web-application/client-react/
target: /usr/src/app/
- type: volume
target: /usr/src/app/node_modules
- type: bind
source: ../08-running-containers/client-react/vite.config.js
target: /usr/src/app/vite.config.js
@ -20,8 +22,10 @@ services:
target: dev
volumes:
- type: bind
source: ../05-example-web-application/api-node/src/
target: /usr/src/app/src/
source: ../05-example-web-application/api-node/
target: /usr/src/app/
- type: volume
target: /usr/src/app/node_modules
init: true
depends_on:
- db

View File

@ -9,19 +9,21 @@ compose-up-d:
###
CIVO_SSH:="ssh://ubuntu@212.2.244.220"
.PHONY: swarm-init
swarm-init:
docker swarm init
DOCKER_HOST=${CIVO_SSH} docker swarm init
.PHONY: 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
swarm-remove-stack:
docker stack rm example-app
DOCKER_HOST=${CIVO_SSH} docker stack rm example-app
.PHONY: create-secrets
create-secrets:
echo -n "foobarbaz" | docker secret create postgres-passwd -
echo -n "postgres://postgres:foobarbaz@db:5432/postgres" | docker secret create database-url -
echo -n "foobarbaz" | DOCKER_HOST=${CIVO_SSH} docker secret create postgres-passwd -
echo -n "postgres://postgres:foobarbaz@db:5432/postgres" | DOCKER_HOST=${CIVO_SSH} docker secret create database-url -

View File

@ -7,8 +7,14 @@ services:
ports:
- 80:80
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/ping"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
api-node:
image: sidpalas/devops-directive-docker-course-api-node:7
image: sidpalas/devops-directive-docker-course-api-node:8
networks:
- frontend
- backend
@ -18,8 +24,14 @@ services:
environment:
- DATABASE_URL=postgres://postgres:foobarbaz@db:5432/postgres
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "src/healthcheck.js"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
api-golang:
image: sidpalas/devops-directive-docker-course-api-golang:6
image: sidpalas/devops-directive-docker-course-api-golang:7
networks:
- frontend
- backend
@ -29,6 +41,12 @@ services:
environment:
- DATABASE_URL=postgres://postgres:foobarbaz@db:5432/postgres
restart: unless-stopped
healthcheck:
test: ["CMD", "/healthcheck"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
db:
image: postgres:15.1-alpine
networks:
@ -37,6 +55,11 @@ services:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=foobarbaz
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:

View File

@ -7,8 +7,14 @@ services:
- frontend
ports:
- 80:80
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/ping"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
api-node:
image: sidpalas/devops-directive-docker-course-api-node:7
image: sidpalas/devops-directive-docker-course-api-node:8
environment:
- DATABASE_URL_FILE=/run/secrets/database-url
secrets:
@ -18,8 +24,14 @@ services:
- backend
ports:
- 3000:3000
healthcheck:
test: ["CMD", "node", "/usr/src/app/healthcheck.js"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
api-golang:
image: sidpalas/devops-directive-docker-course-api-golang:6
image: sidpalas/devops-directive-docker-course-api-golang:7
networks:
- frontend
- backend
@ -29,6 +41,12 @@ services:
- database-url
ports:
- 8080:8080
healthcheck:
test: ["CMD", "/healthcheck"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
db:
image: postgres:15.1-alpine
networks:
@ -41,6 +59,11 @@ services:
- POSTGRES_PASSWORD_FILE=/run/secrets/postgres-passwd
secrets:
- postgres-passwd
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata: