jsramverk.se

jsramverk.se / Docker som utvecklingsmiljö

Docker som utvecklingsmiljö

Skriven av: Emil Folino, Mikael Roos. Uppdaterad: 2019-08-14

Docker logga

Vi skall komma igång med virtualiseringsmiljön Docker och se hur den kan användas som stöd för utveckling och test av programvara.

Du kommer se hur du kan jobba med images och kontainrar samt se hur du kan skapa egna images och underhålla dem i ett Git-repo.

Information om Docker hittar du på docker.com. Låt oss hålla det enkelt och jämföra Docker med virtualiseringsmiljön VirtualBox. Vi kan använda det för att köra andra operativsystem på vår egen dator, eller operativsystem paketerade tillsammans med programvaror. Docker kallar en sådan samling av operativsystem och programvara för en image som körs i en kontainer. Vi kan till exempel starta upp en kontainer som bygger på en image med Linux, Apache, PHP och MySQL, och vi kan använda det utan att behöva installera dessa programvaror på vår egen dator.

Vi börjar med att installera Docker för att se hur det fungerar.

Installera Docker

Webbplatsen för Docker innehåller en del där du kan ladda hem och installera Docker. Det finns en Community Edition (CE) versioner för Windows, Mac och Linux. Kör igenom installationen enligt anvisningarna.

Docker är en virtualiseringsmiljö så den kräver att din datorn är kapabel att köra vissa virtualiseringstekniker.

Du behöver bekanta dig med dokumentationen för Docker. Det är din vägledare för att komma igång med Docker.

Du kan välja att skapa ett konto på Docker. Det kan vara en bra idé om du vill pröva på att göra egna kontainrar. Gör så.

Windows, Docker och bcrypt

Ibland kan kombinationen av Windows, Docker och npm modulen bcrypt ställa till med stora problem. Ett tips hämtat från installationsmanualen för bcrypt är att installare npm paketet windows-build-tools med kommandot nedan. Installera det i kommandotolken (cmd) eller Powershell så Windows har tillgång till det.

$npm install --global --production windows-build-tools

Testa Docker

När du installerat Docker så kommer du åt verktygen via terminalen. Installationsfasen brukar sluta med att du kör följande för att verifiera installationen.

$docker --version
$docker-compose --version
$docker run hello-world
$docker run -it ubuntu bash

Det som händer i sista steget är att vi startar upp en kontainer med Ubuntu och startar bash i kontainern. Optionen -it står för --interactive och -tty, vi startar upp en interaktiv session med kontainern och allokerar en tty som agerar som terminalen.

Lär dig mer om kommandot med den inbyggda hjälpen.

$docker help
$docker help run

Hitta och ladda hem en image

En image är alltså en samling av operativsystemet och programvarar som installerats. Det finns många färdiga images och vi kan söka bland dem på Docker Store. När vi kör dem, eller hämtar dem, så laddas de ned till vår dator och vi kan se vilka som finns installerade lokalt.

$docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
ubuntu                latest              747cb2d60bbe        9 days ago          122MB
hello-world           latest              05a3bd381fc2        5 weeks ago         1.84kB

Om vi söker på Docker Store, till exempel på PHP, så kan vi hitta images som innehåller olika versioner av PHP. Vi kan ladda hem den imagen och starta den.

$docker pull php
$docker images php
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
php                 latest              c342f917459a        10 days ago         371MB

Sedan kan vi köra imagen i en kontainer.

$docker run -it php
$docker run -it php bash

Det var alltså två olika sätt att koppla sig till kontainern på ett interaktivt sätt.

Image har taggar

En image kan ha många taggar som ger olika innehåll. Tittar vi på imagen för PHP så hittar vi olika taggar/versioner, med eller utan Apache installerat. Låt oss pröva att köra en variant som har Apache installerat.

$docker run php:7.1-apache

Nåväl, men hur kopplar jag mig till Apache? Hur får jag en webbsida att visas?

Kör en detached kontainer

Låt oss starta upp kontainern i detached mode och montera katalogen vi står i, som basen för Apache, samt koppla en specifik port 8080 (lokal dator) till Apaches standardport 80 (inuti kontainern).

$docker run --detach --publish 8080:80 --name my-php-app --volume "$PWD":/var/www/html php:7.1-apache

Nu snurrar kontainern och via porten 8080 kan vi nå Apache som snurrar på port 80 inuti kontainern. Om vi skapar en fil i vår lokala katalog så kan vi visa den via curl.

$echo "Moped" > test.php
$curl localhost:8080/test.php
Moped

Ah, nu börjar det se trevligt ut. Vi kan alltså koppla vårt lokala filsystem till kontainerna som kör i sin egen isolerade miljö. Detta ger oss möjligheten att enkelt köra mot många olika versioner av PHP samtidigt. Det kan vara en bra sak när man vill testa om sitt program fungerar i olika versioner.

För att stänga ned kontainern så använder vi docker container.

$docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
1c4add957e71        php:7.1-apache      "docker-php-entryp..."   43 seconds ago      Up 41 seconds       0.0.0.0:8080->80/tcp   myapp
$docker container stop my-php-app

Glöm inte hjälpkommandot.

$docker help container

Flera kontainer till samma filsystem

Det ser ju spännande ut att kunna ha flera kontainerar, med olika version av programvara, körande mot samma volym, samma källkod på disk.

Låt oss göra ett kort test och se hur det fungerar. Jag startar upp två kontainerar med olika versioner av PHP och kopplar dem mot samma katalog på disken.

$docker run --detach --publish 8071:80 --name php71-app --volume "$PWD":/var/www/html php:7.1-apache
$docker run --detach --publish 8070:80 --name php70-app --volume "$PWD":/var/www/html php:7.0-apache

Jag kan se att de är uppe och snurrar.

$docker container ls

Jag skapar en testfil och accessar den via de båda kontainrarna.

$echo '<?php echo PHP_VERSION . "\n";' > version.php
$curl localhost:8071/version.php
$curl localhost:8070/version.php

Det ser ut att fungera bra. Då stänger jag ned kontainrarna.

$docker container stop php71-app php70-app

Det känns som detta kan vara ett bra verktyg för test och utveckling.

Spara konfigurationen för docker-compose

Låt oss se vad kommandot docker-compose kan göra för att underlätta att köra flera servrar, eller kontainrar, samtidigt och låta dem köra mot samma filsystem eller utvecklingsrepo.

Först skapar vi en fil docker-compose.yml som innehåller följande.

version: "3"
services:
    php71:
        image: php:7.1-apache
        ports:
            - "8071:80"
        volumes:
            - ./:/var/www/html/
    php70:
        image: php:7.0-apache
        ports:
            - "8070:80"
        volumes:
            - .:/var/www/html
    php56:
        image: php:5.6-apache
        ports:
            - "8056:80"
        volumes:
            - .:/var/www/html

Sen startar vi samtliga kontainrar med kommandot docker-compose med eller utan detached läge.

$docker-compose up
$docker-compose up -d

Vi kan se att de kör med kommandot docker ps eller docker container.

$docker ps
$docker container ls

Vi kan testa att ladda en fil från respektive server/kontainer.

$curl localhost:8071/version.php
$curl localhost:8070/version.php
$curl localhost:8056/version.php

Sen stänger vi ned dem.

$docker-compose down

Visst känns det rätt behagligt och kraftfullt att jobba med kontainrar?

Att inkludera docker-compose i ett repo

Låt oss kika på ett repo som har inkluderat detta sätt att jobba för att testa repot. Vi kikar på mos/cimage. Vi gör ett snabbt test genom att klona repot och köra igång kontainrar med docker-compose.

$git clone https://github.com/mosbth/cimage.git
$cd cimage
$chmod 777 cache
$docker-compose up -d

Nu kan du öppna länken localhost:8071/webroot/imgd.php?src=car.png i din webbläsare. Öppna sedan två flikar till och justera porten till 8070 respektive 8056.

Du har nu tre flikar som kör mot tre olika installationer och versioner av PHP och testar samma källkod i repot.

Tre flikar mot tre olika installationer och versioner av PHP, samtidigt.

Du kan stänga ned kontainrarna med docker-compose.

Att göra egna images

Om du kikar lite extra på hur mos/cimage använder docker-compose så kommer du se att det är egna images som bygger på och utökar imagen från PHP.

Börja kika på filen docker-compose.yml så ser du referenser till images som heter cimage/php*.

Du kan söka ut dem via Docker Store och du kommer hamna på organisationen cimage.

Klicka på en av imagen, till exempel cimage/php71-apache så kommer du till en sida för den imagen.

Där ser du också en koppling till ett repo cimage/docker på GitHub.

Nu har du hamnat på det repo som skapar och underhåller de tre images som är publicerade på Docker Store.

Det som är viktigt i detta repot är respektive Dockerfile, kika på php71/Dockerfile som ett exempel på hur en image skapas utifrån en annan image.

Det sista som bygger själva imagen, från Dockerfilen, är ett target i Makefilen som heter make build.

Makefilen innehåller också make publish som laddar upp senaste versionen av de images som är byggda.

Ska vi ta det en gång till?

Om du vill skapa egna images och publicera på Docker Store så börjar du med ett eget repo där du underhåller dina Dockerfiles. Du bygger dem och publicerar dem till Docker Store på det sättet som jag visade att Makefilen gjorde.

För att allt skall fungera så behöver du ditt konto på Docker. Så här ser mitt konto ut.

Docker mot olika versioner av Node

Ibland vill man välja vilken version av Node man använder för sina enhetstester. Det kan vara bra att kunna köra testerna mot godtycklig version av Node, eventuellt kombinerad med specifika versioner av andra programvaror. Här kan Docker hjälpa oss.

Kör enhetstester mot olika versioner

I katalogen example/test/unittest-docker har jag förberett ett exempel som kör samma testfall som tidigare men man kan välja att köra dem mot en Docker kontainer som kör en specifik installation av Node.

Du kan köra testerna mot en specifik kontainer på något av följande sätt.

$docker-compose run node_alpine npm test
$npm run docker-latest

Du kan sedan köra testerna mot en äldre version av Node på följande sätt.

# Node version 8
$docker-compose run node_8_alpine npm test
$npm run docker-8

# Node version 6
$docker-compose run node_6_alpine npm test
$npm run docker-6

Nu har du alltså möjligheten att köra tester med samma kodbas mot flera versioner av Node. Det är kraftfullt och kan spara dig tid när du utvecklar och måste ha koll på att koden fungerar på olika versioner.

Tjänsterna kontrolleras av docker-compose

Det är filen docker-compose.yml som styr vilka kontainrar som startas upp. I package.json ligger de kommandon som körs och npm run erbjuder därmed en kortare väg att starta upp testerna mot olika versioner av Node.

Så här ser docker-compose.yml ut för ett par av de tjänster som definieras.

version: "3"
services:
    node_alpine:
        build:
            context: .
            dockerfile: docker/Dockerfile-node-alpine
        volumes:
            - ./:/app/
            - /app/node_modules/

    node_8_alpine:
        build:
            context: .
            dockerfile: docker/Dockerfile-node-8-alpine
        volumes:
            - ./:/app/
            - /app/node_modules/

Filen docker-compose.yml hänvisar till de Docker-filer som används för att bygga respektive image.

När docker-compose använder en image för att starta upp en kontainer så kan den montera volymer och på så sätt länka kontainern till det lokala filsystemet.

Följande rader monterar hela katalogen in i kontainern, men den använder katalogen app/node_modules/ som finns i imagen, även om det finns en lokal katalog som heter node_modules.

volumes:
    - ./:/app/
    - /app/node_modules/

På så vis kan du både ha en lokal installation av alla dina node moduler och det kan finnas en specifik installation i respektive image.

Docker-filer ger images

Grunden till varje image är en Dockerfile som i exemplet ligger i katalogen docker/. De ger basen för imagens innehåll. När en image byggs, i samband med att den refereras av docker-compose, så byggs den i ett context av katalogen vi står i.

build:
    context: .
    dockerfile: docker/Dockerfile-node-alpine

En Dockerfile kan se ut så här, exemplet är från docker/Dockerfile-node-alpine.

# Use a base image
FROM node:alpine

# Create a workdir
RUN mkdir -p /app
WORKDIR /app

# Install npm packages
COPY package.json /app
RUN npm install

De sista raderna kopierar filen package.json från byggets context, in i imagen och därefter körs npm install som blir image-specifik och installeras i /app/node_modules.

Om du gör ändringar i en image-fil kan det kräva att du bygger om den, till exempel via docker-compose build node_alpine.

# Bygg om en image om du gjort ändringar i dess Dockerfile
$docker-compose build node_alpine

Inspektera en image

Du kan starta en terminal (bash, sh) mot en kontainer för att inspektera den och se vad den innehåller.

$docker-compose run node_alpine sh
/app # pwd
/app
/app # ls
coverage            docker              docker-compose.yml
node_modules        package.json        src
test

De images som slutar på alpine innehåller bara sh medans du kan använda bash i de andra som bygger på debian.

Varför alpine versus debian?

Alpine är en minimal bas-image alpine vars storlek är väldigt liten jämfört med en image baserad på debian eller ubuntu.

Här kan du se storleken på de images som bygger på alpine och de andra som bygger på debian.

$docker image ls
REPOSITORY                     TAG         SIZE
unittestdocker_node_latest     latest      690MB
unittestdocker_node_8          latest      695MB
unittestdocker_node_6          latest      682MB
unittestdocker_node_alpine     latest      83.4MB
unittestdocker_node_8_alpine   latest      86.5MB
unittestdocker_node_6_alpine   latest      74.2MB

Som du ser så finns det utrymma att spara med hjälp av alpine-images.

Dubbelkolla vilken version som körs

Om du vill dubbelkolla vilken version av Node som körs i en kontainer, bara för att vara säker, så kan du starta upp en interaktiv session och kontrollera versionerna.

$docker-compose run node_alpine node
> process.version
'v8.8.1'
> process.versions
{ http_parser: '2.7.0',
  node: '8.8.1',
  v8: '6.1.534.42',
  uv: '1.15.0',
  zlib: '1.2.11',
  ares: '1.10.1-DEV',
  modules: '57',
  nghttp2: '1.25.0',
  openssl: '1.0.2l',
  icu: '59.1',
  unicode: '9.0',
  cldr: '31.0.1',
  tz: '2017b' }

Avslutningvis

Vi har gått igenom de första stegen i hur man kommer igång med Docker via kommandot docker och docker-compose. Vi har också sett hur images och kontainrar fungerar och hur de kan användas för att göra en flexibel test- och utvecklingsmiljö kopplad till ditt repo.

Avslutningsvis såg vi hur man kan göra egna images.

Det finns en forumtråd kopplad till denna artikeln. Där kan du ställa frågor eller bidra med tips och trix.