Immagini e linguaggio Go
Immagine con Compilatore Go
Usiamo il metodo manuale:
- partire da un'immagine di base
- aggiungere il software necessario
- clonare il contenitore in una nuova immagine
Partiamo da Alpine:
docker run -ti --name alpine alpine sh
Aggiorniamo l’ambiente e aggiungiamo pacchetti:
apk update
apk add vim
apk add git
Installiamo il supporto go per vim da Github:
cd
git clone https://github.com/fatih/vim-go.git \
~/.vim/pack/plugins/start/vim-go
Installiamo go:
apk add go
apk add musl-dev
Alpine usa la libreria runtime MUSL non GLIBC. Occorre installarne il pacchetto di sviluppo.
Prepariamo lo scaffolding di go:
su -
mkdir -p ~/go/src ~/go/bin ~/go/pkg
Aggiunta dei plugin go a vim. Lanciare vim:
vim
Dare il comando vim (preso dal supporto installato)
:GoInstallBinaries
La possibilità di dare comandi interattivi uno dei motivi per cui alle volte si preferisce una costruzione manuale di immagine a quella tramite Dockerfile. Quest'ultimo permette solo l'esecuzione di comandi batch.
Attendere il completamento e uscire da vim con :q Enter
Prepariamo l’ambiente editando /root/.profile
:
cat <<EOF >> /root/.profile
export GOROOT=/usr/lib/go
export GOPATH=/root/go
export GOBIN=\$GOPATH/bin
export GO111MODULE=off
export PATH=~/bin:\$GOBIN:\$GOROOT/bin:\$PATH
EOF
Uscire dal contenitore con Ctrl-P Ctrl-Q, NON col comando exit
.
Sulla macchina host generiamo la nuova immagine:
docker commit alpine vimgo
Attenzione: è grosso, ci vuole tempo.
Uso di vimgo
Creare una directory locale da condividere:
mkdir -p ~/bin
Lanciare il contenitore:
docker run --rm -ti --name vimgo \
-v ~/bin:/root/go/bin vimgo su -
Il comando eseguito, su -
, lancia una shell di login di root, che dal .profile
setta tutte le variabili d'ambiente.
E' necessario eseguire su -
perchè se si esegue semplicemente sh
parte una shell senza ambiente.
Scrittura di un semplice programma in Go:
cd go/src
mkdir hello
cd hello
vim hello.go
package main
import "fmt"
func main() {
fmt.Println("vim-go")
}
Compilazione statica:
go build --buildmode=exe --ldflags '-linkmode external \
-extldflags "-static" -s' hello.go
Copia nella cartella condivisa:
cp hello ~/go/bin
Uscire dal contenitore con exit
.
Il contenitore è automaticamente rimosso poichè era stato lanciato con l'opzione --rm
.
Provare in locale:
~/bin/hello
Immagine e Go
Produrremo un altro eseguibile Go linkato staticamente e lo inseriremo in un'immagine.
Preparazione del progetto:
mkdir -p ~/docker/ex/go
cd ~/docker/ex/go
Partenza di Vimgo:
```bash
docker run --rm -ti --name vimgo \
-v $PWD:/root/go/bin vimgo su -
Questa volta mappiamo la directory corrente a quella degli eseguibili Go sul contenitore.
Prepariamo lo scaffolding:
cd go/src
mkdir helloworld
cd helloworld
vim helloworld.go
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World from Go in Docker")
}
func main() {
http.HandleFunc("/", helloHandler)
fmt.Println("Started, serving at 8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic("ListenAndServe: " + err.Error())
}
}
E’ un web server sulla porta 8080 che da un semplice messaggio.
Compilazione statica:
go build --buildmode=exe --ldflags '-linkmode external \
-extldflags "-static" -s' helloworld.go
Copia nella cartella condivisa:
cp helloworld ~/go/bin
Uscire dal contenitore con exit
. Il contenitore è automaticamente rimosso.
Generazione dell'Immagine
Dovrebbe esserci nella directory corrente il file eseguibile helloworld
, prodotto col contenitore.
Scrivere il Dockerfile:
vim Dockerfile
FROM scratch
ADD ./helloworld /helloworld
EXPOSE 8080
CMD ["/helloworld"]
scratch
è il nome di un'immagine vuota.
Un programma Go è già il 99% di un’immagine.
Solitamente si parte da un'immagine di base perche fornisce uno scheletro di ambiente operativo di cui il nostro applicativo ha bisogno: librerie, ma anche aggancio a funzionalità del kernel, come gestione memoria e schedulazione.
Un eseguibile Go linkato staticamente non ha bisogno di librerie. Inoltre è fornito di serie di un monitor operativo, nello spazio user, che fornisce:
- schedulazione concorrente
- allocazione di memoria
- garbage collection
Non ha quindi bisogno neppure di uno stub di sistema operativo sottostante.
Compiere il build:
docker build -t greet .
Lanciare un container per testare:
docker run -d --rm --name saluti greet
Controllare alla URL: localhost:8080.
Al termine fermare il container che verrà automaticamente rimosso:
docker stop saluti
Chain Build
Possiamo compiere due migliorie:
- usare un'immagine per il compilatore Go già disponibile su Docker Hub, anzichè la nostra
vimgo
- compiere la compilazione e subito la preparazione di un'immagine finale con un unico Dockerfile, usando la tecnica del chain build
Preparare un programma demo:
mkdir -p ~/docker/ex/demo
cd ~/docker/ex/demo
vim main.go
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, 世界")
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("Running demo app. Press Ctrl+C to exit...")
log.Fatal(http.ListenAndServe(":8888", nil))
}
Preparare il Dockerfile.
vim Dockerfile
FROM golang:1.17-alpine AS build
WORKDIR /src/
COPY main.go go.* /src/
ENV GO111MODULE="off"
RUN CGO_ENABLED=0 go build -o /bin/demo
FROM scratch
COPY --from=build /bin/demo /bin/demo
EXPOSE 8888
ENTRYPOINT ["/bin/demo"]
Build dell’immagine myhello
:
docker build -t myhello .
Test dell'immagine:
docker run -d --rm --name try -p 8888:8888 myhello
Connessione al contenitore:
curl localhost:8888
Al termine del test, stop del contenitore, che lo rimuove.