Asi každý z nás sa snaží vytvoriť čo najmenší a najbezpečnejší kontajner aplikácie. Aké možnosti máme a existuje univerzálne riešenie?
Nedávno som si položil otázku či by som mal ostať pri Alpine alebo ísť cestou tzv. distroless kontajnerov. A je tu ešte nejaká ďalšia možnosť?
Alpine distribúciu asi všetci poznáte. Ale, čo to je distroless kontajner?
Začneme najprv problémom. Distribúcie ako Alpine obsahujú ďaleko viac ako len
aplikáciu. A každá vec, ktorá tam je naviac, nielen zvyšuje veľkosť obrazu, ale
aj bezpečnostné riziko. Preto Google prišiel so sadou tzv. distroless kontajnerov.
Ich súčasťou nieje nič, len samotná aplikácia a potrebné knižnice. Žiaden
bash
, žiaden cp
.
Použitie je veľmi jednoduché. Stačí ak FROM
odkazuje na ten správny distroless
kontajner.
FROM gcr.io/distroless/nodejs:16
...
Lenže, je distroless skutočne bezbečnejší a menší kontajner, ako napríklad bežne používaný Apline? Alebo ide len o hype?
Urobil som 2 experimenty. Jeden pre NodeJS a druhý pre Go aplikáciu. Pre obe aplikácie som vytvoril dvojicu kontajnerov. Jeden ako Alpine a druhý ako distroless. Následne som sa pozrel na ich veľkosť a tiež počet zraniteľnosti.
Trivy
Na detekciu zraniteľnosti som použil Trivy s aktuálnou databázou zraniteľnosti. Ide o malý užitočný scanner od spoločnosti AquaSecurity, ktorá je známa v oblasti bezpečnosti kontajnerov. Trivy prescanuje obraz kontajnera a zo svojej databázy vypíše aktuálny zoznam tzv. CVE ID. CVE je skratka pre Common Vulnerabilities and Exposures a ide o akýsi zoznam verejne dostupných bezpečnostných zraniteľnosti.
Samozrejme existujú aj lepšie nástroje. Trivy som si však zvolil kvôli jeho jednoduchosti.
Treba si uvedomiť, že zraniteľnosti sa menia v čase a pribúdajú. To, čo platilo v dobe, keď som robil tento test, vôbec nemusí platiť o týždeň.
NodeJS aplikácia
Ale späť k experimentovaniu. Vytvoril som si veľmi jednoduchú NodeJS aplikáciu:
const http = require('http');
const requestListener = function (req, res) {
res.writeHead(200);
res.end('Hello, World!');
}
const server = http.createServer(requestListener);
server.listen(8080);
Následne som pre aplikáciu vytvoril klasický Alpine kontajner:
Dockerfile.alpine
FROM node:16-alpine
COPY hello.js /
WORKDIR /
EXPOSE 8080
CMD ["hello.js"]
$ docker build . -f Dockerfile.alpine -t hello-alpine
Okrem toho som si vytvoril aj distroless kontajner. Ten sa líši od Alpine tým, čo ma vo FROM
:
Dockerfile.distroless
FROM gcr.io/distroless/nodejs:16
COPY hello.js /
WORKDIR /
EXPOSE 8080
CMD ["hello.js"]
$ docker build . -f Dockerfile.distroless -t hello-distroless
Bohužiaľ, momentálne nebol dostupný distroless kontajner pre NodeJS 17, preto som použil to posledné, čo bolo dostupné, čiže NodeJS 16. Tú istú verziu som použil aj pre Alpine.
Takže teraz mám v systéme dvojicu obrazov kontajnerov hello-alpine
a hello-distroless
Poďme sa pozrieť, ako si na tom stoja s veľkosťou. Je distroless menší?
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-alpine latest 7ee7f2728fcb 18 seconds ago 110MB
hello-distroless latest 277e5bff2d6e About a minute ago 116MB
Alpine má teda 110MB a distroless má 116MB a je zaujímavo väčší.
Poďme sa pozrieť, ako si na tom stoja s bezpečnosťou? Oba obrazy preskenujem
pomocou trivy
:
$ trivy image hello-distroless:latest
2021-11-27T12:07:53.147Z INFO Detected OS: debian
2021-11-27T12:07:53.147Z INFO Detecting Debian vulnerabilities...
2021-11-27T12:07:53.150Z INFO Number of language-specific files: 1
2021-11-27T12:07:53.150Z INFO Detecting node-pkg vulnerabilities...
hello-distroless:latest (debian 11.1)
=====================================
Total: 13 (UNKNOWN: 0, LOW: 11, MEDIUM: 0, HIGH: 1, CRITICAL: 1)
Node.js (node-pkg)
==================
Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 2, CRITICAL: 0)
$ trivy image hello-alpine
2021-11-27T12:10:44.822Z INFO Detected OS: alpine
2021-11-27T12:10:44.822Z INFO Detecting Alpine vulnerabilities...
2021-11-27T12:10:44.823Z INFO Number of language-specific files: 1
2021-11-27T12:10:44.823Z INFO Detecting node-pkg vulnerabilities...
hello-alpine (alpine 3.14.3)
============================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
Node.js (node-pkg)
==================
Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 2, CRITICAL: 0)
Ako je vidieť, Distroless má 15 zraniteľností a Alpine má 2. Ako je vidieť distroless je v tomto prípade nielen väčší, ale dokonca obsahuje viac zraniteľností.
Go aplikácia
Ten istý experiment teraz skúsim s Go aplikáciou. Opäť veľmi triviálny kód:
package main
import "fmt"
import "net/http"
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello\n")
}
func main() {
http.HandleFunc("/hello", hello)
http.ListenAnsServe(":8080",nil)
}
Opäť som si vytvoril dvojicu obrazov hello-go-alpine
:
FROM golang:1.17 as build-env
WORKDIR /go/src/app
COPY *.go .
COPY go.mod .
RUN CGO_ENABLED=0 go build -o /go/bin/test
FROM alpine:3.14
EXPOSE 8080
COPY --from=build-env /go/bin/test /
CMD ["/test"]
$ docker build . -f Dockerfile.alpine -t hello-go-alpine
A ešte hello-go-distroless
:
FROM golang:1.17 as build-env
WORKDIR /go/src/app
COPY *.go .
COPY go.mod .
RUN CGO_ENABLED=0 go build -o /go/bin/test
FROM gcr.io/distroless/static
EXPOSE 8080
COPY --from=build-env /go/bin/test /
CMD ["/test"]
Podobne, ako pri NodeJS skontrolujem veľkosť obrazov jednoducho cez Docker:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-go-alpine latest 94c15ff84c66 About an hour ago 11.7MB
hello-go-distroless latest 059739d7485a About an hour ago 8.63MB
Tu vidieť, že čísla hrajú v prospech distroless. Alpine má cca 11MB a distroless má 8MB. Ale, ako si na tom stojí s bezpečnosťou?
$ trivy image hello-go-alpine
2021-12-01T15:51:07.388Z INFO Detected OS: alpine
2021-12-01T15:51:07.388Z INFO Detecting Alpine vulnerabilities...
2021-12-01T15:51:07.393Z INFO Number of language-specific files: 0
hello-go-alpine (alpine 3.14.3)
===============================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
$ trivy image hello-go-distroless
2021-12-01T15:51:46.400Z INFO Detected OS: debian
2021-12-01T15:51:46.400Z INFO Detecting Debian vulnerabilities...
2021-12-01T15:51:46.400Z INFO Number of language-specific files: 0
hello-go-distroless (debian 11.1)
=================================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
Tu si oba obrazy stoja na rovnako. Žiadna známa zraniteľnosť.
docker-slim
Ako ďalší experiment som sa rozhodol na existujúce kontajnery použiť nástroj docker-slim. Ide o nástroj na optimalizáciu kontajnerov. Jeho cieľom je presne to, o čo sa snažíme: Vytvoriť najmenší a najbezpečnejší kontajner.
Ako si poradí s NodeJS a Go? Aplikoval som ho na moje obrazy kontajnerov:
$ docker-slim build hello-nodejs-distroless
$ docker-slim build hello-nodejs-alpine
$ docker-slim build hello-go-distroless
$ docker-slim build hello-go-alpine
`
Ako prvé som si pozrel veľkosť obrazov:
$ docker images
hello-nodejs-alpine latest 910726cd68ae 29 minutes ago 110MB
hello-nodejs-alpine.slim latest da71d65b80cf 60 seconds ago 84.2MB
hello-nodejs-distroless latest 4f181315bc44 30 minutes ago 116MB
hello-nodejs-distroless.slim latest db135e6d457e 20 seconds ago 86.5MB
$ docker images
hello-go-alpine latest 1fa283bc7deb 1 day ago 11.7MB
hello-go-alpine.slim latest 3af934521aff 1 day ago 6.07MB
hello-go-distroless latest 04842032767c 1 day ago 8.43MB
hello-go-distroless.slim latest e55051e81cc6 1 day ago 6.07MB
Nástroj vytvoril ku každému obrazu jeho .slim
verziu a výsledok je celkom
potešujúci. V oboch prípadoch dokázal zosekal veľkosť. U NodeJS to je cca na
84-86MB. U Go aplikácie na 6MB.
A čo na to Trivy? Ako je na tom slim verzia z pohľadu bezpečnosti?
$ trivy hello-nodejs-alpine.slim
2021-12-03T15:59:21.706+0100 INFO Number of language-specific files: 0
$ trivy hello-nodejs-distroless.slim
2021-12-03T16:00:10.902+0100 INFO Number of language-specific files: 0
V oboch prípadoch sa stalo niečo zaujímavé. Trivy nemal čo analyzovať. Dá sa to považovať za bezpečný kontajner? Ja by som sa na to nespoliehal a zrejme by som mal zvážiť lepší, možno komerčný nástroj.
Ale, čo stojí za týmto zázrakom? Ako sa to podarilo?
Nástroj kombinuje statickú a dynamickú analýzu kontajnera. Práve dynamická analýza je zaujímavá. Nástroj spustí kontajner a pomocou rôznych sond sa pokúša analyzovať systémové volania. Práve takto sa snaží identifikovať všetký potrebné knižnice a súbory potrebné pre beh aplikácie.
A práve aj tu môže nastať kameň úrazu. Ak aplikácia robí nejaké dynamické nahrávanie a sonda nevyvolá danú vetvu kódu, potom docker-slim môže odstrániť súbor, ktorý nakoniec bude potrebný. Preto sa aj v dokumentácii uvádza, že je potrebné si kontajner pretestovať. Pozor teda na rôzne lazy loadingy.
Záver
Je lepšie Distroless? Alebo je to zbytočný hype a človek by mal ostať pri Alpine? Je docker-slim to vysnené riešenie?
Na túto otázku neexistuje univerzálna odpoveď. Ako vidieť výsledok je rôzny. Distroless nie je zárukou bezpečnosti a najmenšieho možného obrazu. Aj Alpine dokáže prekvapiť. A hoci docker-slim dokáže skutočne malý zázrak, je potrebné kontajnerizovanú aplikáciu dôkladne otestovať.
Platí len jedno. Každý obraz treba individuálne zvážiť. Treba si zmerať, ktorá technika má význam. Bez dát a meraní ide len o subjektívne tvrdenie.
Čo sa týka bezpečnosti. Ak chceme skutočne docieliť čo najbezpečnejší
kontajner, je potrebné do delivery pipeline zahrnúť skenovanie obrazov práve
pomocou nástrojov, ako je snyk
či trivy
.