Docker image untuk aplikasi berbasis Golang biasanya mempunyai ukuran yang cukup besar, untuk keperluan digunakan pada sebuah Containers as a service (CaaS) akan memakan waktu untuk diupload.

Kamu dapat memperkecil ukuran image-nya dengan menggunakan konsep multi-stage builds, sehingga untuk aplikasi berbasis Golang berikut Supervisor dan Cron hanya berukuran sekitar 100 MB.

1: Struktur


Struktur yang sesuai dari aplikasi berbasis Golang kamu sangat penting dalam hal ini, berikut struktur dasar folder dan file yang dapat kamu terapkan:

-> project-kamu/
----> resources/ (opsional)
----> storage/ (opsional)
----> public/ (opsional)
----> web/ (opsional)
----> cron-scripts/
--------> my-script.sh
----> supervisor-scripts/
--------> my-script.sh
----> logs/
--------> cron/
--------> supervisor/
----> src/
--------> go.mod
--------> go.sum
--------> main.go
----> .dockerignore
----> config.yml
----> cron-definition
----> docker-entrypoint
----> Dockerfile
----> supervisord.conf

Semua kode Golang hanya berada didalam folder “src” karena nantinya akan kita compile menjadi binary. Sedangkan seperti folder “resources”, “storage”, “public”, dan “web” berada diluar dikarenakan bersifat hanya akan digunakan ketika aplikasi dijalankan. Mengenai “config.yml” terserah kebutuhan aplikasi kamu dapat berada didalam folder “src” atau diluar.

2: Dockerfile


Didalam Dockerfile perlu menerapkan konsep multi-stage builds supaya ukurannya kecil. Jika kamu yang pernah melakukan hal tersebut, mungkin akan mempunyai isi seperti berikut:

################################
# STEP 1 build executable binary
################################
FROM golang:1.15.6-alpine AS builder

WORKDIR $GOPATH/src/github.com/your-org/your-project

# Another definition
..................

# Build the binary
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
    -ldflags='-w -s -extldflags "-static"' -a \
    -o /go/bin/your-project .

############################
# STEP 2 build a small image
############################
FROM scratch

# Copy our static executable
COPY --from=builder /go/bin/your-project /go/bin/your-project

WORKDIR $GOPATH/dist/github.com/your-org/your-project

# Run the binary.
ENTRYPOINT ["/go/bin/your-project"]

Ukuran Docker image dari definisi diatas akan sangat kecil. Tetapi dalam artikel ini kita juga membutuhkan untuk memasang Supervisor dan Cron, sehingga yang berbeda adalah pada STEP 2, yaitu kamu tidak bisa menggunakan FROM scratch, kamu bisa ganti menggunakan FROM alpine:latest, yaitu seperti berikut:

################################
# STEP 1 build executable binary
################################
FROM golang:1.15.6-alpine AS builder

WORKDIR $GOPATH/src/github.com/your-org/your-project

# Copy and download dependency using go mod
ENV GO111MODULE=on
COPY ./src/go.mod .
COPY ./src/go.sum .
RUN go mod download

COPY ./src/ .

# Build the binary
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
    -ldflags='-w -s -extldflags "-static"' -a \
    -o /go/bin/your-project .

############################
# STEP 2 build a ready image
############################
FROM alpine:latest

# Copy our static executable
COPY --from=builder /go/bin/your-project /go/bin/your-project

COPY docker-entrypoint /docker-entrypoint

RUN apk --update --no-cache add tzdata \
    ca-certificates \
    busybox-suid \
    supervisor && \
    update-ca-certificates && \
    rm -rf /var/cache/apk/* && \
    chmod +x /docker-entrypoint

ENTRYPOINT ["/docker-entrypoint"]

3: ENTRYPOINT


ENTRYPOINT akan dieksekusi saat nanti container dibuat dari image diatas. Didalam file docker-entrypoint kamu isi seperti berikut:

#!/bin/sh

# Create folder to store cron and supervisor logs
mkdir -p /var/log/supervisor
mkdir -p /var/log/cron

# Append custom cron definition into root crontab
grep -qxF '# custom cron schedules' /etc/crontabs/root || cat /go/dist/github.com/your-org/your-project/cron-definition >> /etc/crontabs/root

# Run
/usr/sbin/crond -b & \
/usr/bin/supervisord -c /go/dist/github.com/your-org/your-project/supervisord.conf

4: Cron


Cron akan dijalankan oleh user root, maka isi cron-definition nantinya akan di-append kedalam file /etc/crontabs/root, isi file seperti berikut:

# custom cron schedules
*/10    *       *       *       *       /go/dist/github.com/your-org/your-project/cron-scripts/my-script.sh >> /var/log/cron/my-cron-script.log 2>&1

5: Supervisor


Siapkan isi dari file supervisord.conf seperti berikut:

[supervisord]
logfile=/var/log/supervisor/supervisord.log     ; supervisord log file
logfile_maxbytes=50MB                           ; maximum size of logfile before rotation
logfile_backups=10                              ; number of backed up logfiles
loglevel=error                                  ; info, debug, warn, trace
nodaemon=true                                   ; run supervisord NOT as a daemon
user=root                                       ; default user
childlogdir=/var/log/supervisor/                ; where child log files will live

[supervisorctl]

[inet_http_server]
port=127.0.0.1:9001

[rpcinterface:supervisor]
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface

[program:my-custom-program-name]
command=/bin/sh /go/dist/github.com/your-org/your-project/supervisor-scripts/my-script.sh
user=root
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/my-script.log
stderr_logfile=/var/log/supervisor/my-script_error.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=10

6: Siap dipakai


Sekarang build docker image terlebih dahulu dengan perintah sebagai berikut:

docker build --tag=project-kamu:1.15.6-1.0 .

Kemudian lanjutkan membuat container-nya dengan perintah sebagai berikut:

docker run --interactive --tty \
--name=my-project \
--memory=512m \
--volume=/path/to/project-kamu/logs/cron:/var/log/cron \
--volume=/path/to/project-kamu/logs/supervisor:/var/log/supervisor \
--volume=/path/to/project-kamu:/go/dist/github.com/your-org/your-project \
--detach \
--net=host \
project-kamu:1.15.6-1.0