HA Kluster med CoreOS & Docker Magnus Persson 2014-08-01 Intro Många har troligen varit med om att deras tjänster gått ner på grund av olika anledningar. Det kan vara allt från tillfälliga problem hos leverantören av tjänsten till buggar i applikationen. Med hjälp av ett CoreOS kluster kan man komma runt de här problemen genom att sprida ut sin applikation på klustrerade servrar. CoreOS är en Linux distribution som är byggt för att skala upp och ner på ett snyggt sätt med minimal overhead vad gäller systemresurser. CoreOS är ett open source project. Eftersom CoreOS fortfarande är i beta-stadie så finns en hel del workarounds och saker som förväntas bli bättre i kommande versioner. Det som skiljer sig från en vanlig Linux distribution är att det saknas pakethanterare och alla applikationer är tänka att köra via docker containers. Docker bygger på LXC (Linux Containers) som är slags virtualisering på operativsystemsnivå som gör det möjligt att köra flera isolerade instanser av Linux på en och samma host (behöver ej vara samma Linux distribution som underliggande OS). CoreOS använder sig av en distribuerad version av system init, som heter systemd. Det använder sig även av etcd som är en distribuerad key-value-store där man kan spara applikationsspecifik information som alla noder i klustret kan skriva/läsa. I den här artikeln skapas ett enkelt testkluster på Ipeer Hosted VMware. För enkelhetensskull så skapas i det här fallet alla noder på samma plattform, vilket inte är att rekomendera i en sådan här uppsättning där man bör sprida sina noder på olika plattformar (t.ex. dedikerad server eller en annan molntjänst). En docker container kommer att skapas med en NodeJS applikation som presenterar ett litet webbaserat spel. Applikationen har även en inbyggd webserver. CoreOS Installation Steg 1 är att installera CoreOS. Det här går att göra på flera sätt, här har vi valt via ISO fil som laddas upp på vCloud och monteras till en ny VM. Välj statiskt IP manuellt och välj ett ledigt IP till servern. Dock så får maskinen vid start ett IP via DHCP även om man valt ett specifikt IP. Detta gör inget då vi använder vCloud consolen för att komma åt vår VM. Då den bootar upp så får man ett skal där man kan installera CoreOS till disk genom att köra: coreos-install -d /dev/sda När installationen är klar så är det bara tills vidare att stoppa servern och avmontera ISO filen. Nu kommer ett liten gotcha. CoreOS installeras utan möjlighet att logga in. CoreOS läser sin konfiguration från en så kallad cloud-config fil. Det är en YAML fil som definierar konfiguration som noden ska använda sig av. I den här filen kan man även konfigurera användare samt lägga in SSH nycklar (även från git-hub!). För enkelhetens skull har vi valt att använda lösenord, men rekommenderar att använda SSH nycklar. Cloud-config filen ser ut så här: #cloud-config hostname: node coreos: etcd: name: node discovery: https://discovery.etcd.io/c7c57c96841f7f13887fcad4bd904b6d addr: 172.16.0.10:4001 peer-addr: 172.16.0.10:7001 update: reboot-strategy: etcd-lock fleet: metadata: provider=ipeer units: - name: etcd.service command: start - name: fleet.service command: start - name: 10-network.network content: | [Match] Name=ens* [Network] Address=172.16.0.10/24 Gateway=172.16.0.1 DNS=8.8.8.8 users: - name: core passwd: $6$SALT$pt6375Q0FdjCkYhFrsbS9oG0cmFcWtD/.t5v6CBUm.XFsjZn1AgxQHELLPLNBmdAluDsGrze06HdFR4rMAJ AX1 groups: - sudo - docker Notera att det är väldigt viktigt med att ha indenteringen korrekt I YAML filer, en tab eller fel antal mellanslag kan göra filen korrupt. I filen ovan specifieras host-namnet på noden via 'hostname' (I det här fallet används 'node' följt av inkrementellt nummer). IP adressen kan sättas via variabeln $public_ipv4 eller $private_ipv4 men det fungerar inte så bra i nuvarande version ifall man vill köra DHCP. Därför har vi även valt att använda statiskt IP. En viktig del är att använda sig av en discovery vilket det finns en publik tjänst för där man kan skapa en ny nyckel genom att gå till: https://discovery.etcd.io/new. Då generas en unik nyckel som man sedan kan använda som discovery nyckel med adressen: https://discovery.etcd.io/<nyckel> Går man sedan till den adressen kan man se vilka noder som är med I klustret. Nedan visas en bild där ingen nod är med I klustret, ännu. En till sak som är lite unikt med CoreOS är att det uppdaterar sig själv och startar då automatiskt om. Därför konfiguras en uppdateringsstrategi, I det är fallet 'etcd-lock' vilket innebär att klustret ser till att inte starta om sig ifall någon annan nod är nere eller för tillfället uppdaterar sig, så att klustret hela tiden har minst en aktiv nod. Efter detta specifieras ett antal systemd units som ska starta, i det här fallet fleet (som kommer hanteras lite senare I den här artikeln) samt etcd. Vi specifierar även nätverksinställningar så som IP och en DNS server, I det här fallet Googles 8.8.8.8 DNS server. Sist konfiguerar vi upp en användare med lösenord som är en hash av lösenordet. I skrivande stund så har fleet bara stöd för hårdkodade användaren ”core”, även om det går att skapa andra användare. Hash av lösenordet går att generera t.ex. med perl: perl -e 'print crypt("<ditt lösenord>","\$6\$SALT\$") . "\n"' Nu när cloud-config är skapad så ska den läggas in på noderna. Detta medför lite pyssel eftersom vi inte kan logga in och lägga in manuellt (vid manuellt så ska cluster-config placeras i /usr/share/oem/cluster-config.yml på noderna). Men vi kan skapa en ISO fil med konfigurationen som görs enligt instruktionerna för ”Config Drive” (https://github.com/coreos/coreoscloudinit/blob/master/Documentation/config-drive.md). Det görs på följande sätt: nergal@calypso:~/Work/CoreOS/cluster$ ls new-drive/openstack/latest/user_data new-drive/openstack/latest/user_data nergal@calypso:~/Work/CoreOS/cluster$ mkisofs -R -V config-2 -o configdrive.iso new-drive/ I: -input-charset not specified, using utf-8 (detected in locale settings) Total translation table size: 0 Total rockridge attributes bytes: 677 Total directory bytes: 4458 Path table size(bytes): 40 Max brk space used 22000 178 extents written (0 MB) nergal@calypso:~/Work/CoreOS/cluster$ ls configdrive.iso configdrive.iso nergal@calypso:~/Work/CoreOS/cluster$ 'user_data' innehåller cloud-config datat. 'configdrive.iso' kan sedan laddas upp till vCloud som en vanlig ISO fil. Den monteras sedan till CoreOS VM:en och efter en omstart så laddas konfigurationen in automatiskt och vi kan nu logga in med den skapade användaren. Här ser vi även att vårt statiska IP är satt (172.16.0.10). Då har vi vår första CoreOS server startad. Upprepa samma procedur för andra noden och konfigurera om cloud-config (user_data) filen att innehålla nytt hostname/nodename samt annat IP. Det går även att kopiera node1 via vCloud 'copy-to' till en ny server och konfigurera om denna samt ladda in cloudconfig filen för node2. Vi skapar även en tredje server, kallad node3, för att enkelt kunna visa hur failover fungerar senare. När vi väl startat alla våra servrar kan man gå till discovery URL:en och se så att alla servrar är startade I klustret. Andra bilden visas våra servrar i vCloud kontrollpanelen. Docker Containers Nu ska vi fixa oss en docker image för vårt spel som vi ska hosta på klustret. Notera att en dockerinstans kan innehålla nginx, apache eller vad man nu vill köra. I vårt fall har vi en nodejs server som skall köras. Först skapar vi en docker konfigurationsfil för vår nya image som ska döpas till Dockerfile. Docker containern kommer köra Ubuntu 14.04 och behöver nodejs samt sqlite3. Vi vill även att containern ska publicera porten 443 (som nodejs servern lyssnar på). Vi säger sedan till docker att lägga in allt i katalogen vi står i, vilket är hela applikationen. Vi specifierar även vad som skall exekveras då containern startas, I det här fallet ”server.js”. Filen ser ut så här: # DOCKER-VERSION 0.9.1 FROM ubuntu:14.04 RUN apt-get update RUN apt-get install -y npm nodejs sqlite3 ADD . / EXPOSE 443 CMD ["nodejs", "/server/server.js"] Ubuntu 14.04 hämtas från Dockers bas-registry som hanteras av docker teamet och anses som 'trusted'. I det är fallet kommer vi använda vårt egna publika registry konto för att hantera den image vi ska använda. Notera att man kan enkelt skapa ett eget privat repository på Dockers hemsida. Vi bygger en ny image och pushar upp på vårt konto (efter att ha loggat in med 'docker login'): sudo docker.io build -t lallassu/saving-nemo . sudo docker.io push lallassu/saving-nemo nergal@calypso:~$ sudo docker.io images REPOSITORY TAG IMAGE ID lallassu/saving-nemo latest 1ca2d5337ea4 CREATED 3 minutes ago VIRTUAL SIZE 420.5 MB Där har vi nu vår applikation som en docker container som skall köras på CoreOS klustret. För att se så att den går att nå från vårt kluster kan vi logga in på CoreOS servern och hämta hem den: docker pull lallassu/saving-nemo Sedan kan vi även testa att containern funkar att starta på noden (se bild): docker run –rm -t lallassu/saving-nemo NodeJS servern startas som förväntat. Men vi vill inte starta docker containern manuellt och den ska även ha HA samt starta om ifall den kraschar. Det är här fleet kommer in i bilden. Fleet är ett abstraktionslager till systemd och ser till att en service körs på vissa noder baserat på några enkla regler man specifierar. Det kan vara regler som att man inte vill att en viss typ av docker container körs på samma nod som en av samma typ utan ska vara på åtskilda noder. Vi kan även specifiera hur en container ska startas om, om något ska göras innan uppstart etc. Man kan även spcecifiera med meta-data vart vissa typer ska köra och vilka krav de har. Fleet konfigureras med unit filer som har samma syntax som systemd unit filer. I vårt fall har vi skapat en fil som först specifierar att innan uppstart ska senaste imagen hämtas ifall det finns någon nyare från docker registry. Sedan konfigureras hur containern ska stoppas och startas. Vår unit fil (döpt till nemo.1.service) ser ut som följer: [Unit] Description=nemo-server After=docker.service Requires=docker.service [Service] ExecStartPre=/usr/bin/docker pull lallassu/saving-nemo ExecStart=/usr/bin/docker run --name nemoserver --rm -p 8085:443 lallassu/saving-nemo ExecStop=/usr/bin/docker stop -t 1 nemoserver Restart=always [X-Fleet] X-Conflicts=nemo.*.service X-ConditionMachineMetadata=provider=ipeer Här har vi specifierat att vår service måste starta efter att docker servicen har startat då det är ett krav för att vi ska kunna starta containern. Vi specifierar att den externa porten för containern ska vara 8085 samt sätter ett namn på containern (nemoserver). Vi säger även att den ska startas om vid failure, hur den än falerar. Den här konfigurationen är egentligen systemd konfiguration. Det som är fleet specifikt kommer under X-Fleet delen. Här säger vi att servicen inte får köra på samma nod som någon annan av samma service samt att noden som den körs på måste ha provider ipeer (meta-data specifierade vi i cloud-config filen för CoreOS tidigare). Via meta-data kan vi specifiera vilken typ av hårdvara noden har eller vart i världen noden kör och sedan basera våra val av instanser på den specifierade meta-datan. Exempelvis ifall vi bara vill köra på noder i Sverige med dubbla cores skulle vi kunna specifiera metadatan ”country=sweden,vcpu=2” och sedan sätta detta som ett krav (X-ConditionMachineMetaData) i vår unit-fil. För att aktivera en unit fil med fleet används 'fleetctl' på en node. Samma output kommer visas på alla noder i klustret. Här är några användbara kommandon för fleet. fleetctl start <unit file> # starta unit fleetctl stop <unit file> # stoppa unit fleetctl list-machines -l # lista alla noder I klustret fleetctl list-units # lista alla units fleetctl journal <unit file> # loggar för en unit fleetctl ssh <machine_id> #logga in via ssh på en nod (kräver ssh-agent samt endast med ssh-nycklar) Vi kan nu testa att starta vår unit, se bild: Som vi ser på bilden startas den här uniten på 172.16.0.10 noden. För att skapa en likadan unit så är det bara att kopiera filen nemo.1.service till nemo.2.service utan att göra någon ändring i den. Vi kan sedan starta upp även denna service, se bild nedan: Man kan då se att vår andra service-unit startas på en annan nod vilket stämmer med vår konfiguration där ingen service av typen nemo.x.service fick köras på samma nod (X-Conflicts=nemo.*.service). Vi kan nu testa att stoppa vår container med docker (docker stop <id>), vilket triggar fleet att starta om servicen. Som vi ser på bilden nedan så startas docker containern (unit) om direkt. Eftersom vi har 3 noder i klustret så ska vi se om en failover fungerar. Vi startar om node3 (172.16.0.12). Den service-unit som körs (nemo.2.service) där kommer starta upp på node2 (172.16.0.11). Eftersom containern inte har körts tidigare på node2 så måste docker imagen hämtas från docker registry vilket tar lite tid, där av ligger servicen i 'start-pre' status ett tag. Detta kommer inte ske i omstarter i framtiden (om ingen image uppdatering är gjord). Efter start-pre går servicen upp i running. Lastbalanserare Nu när vårt kluster är uppe behöver vi ha en lastbalanserare framför som använder sig av vårt kluster. Det finns flera alternativ men I vårt fall använder vi vClouds ingbyggda och skapar en ny pool med våra klusternoder. Under Edge Gateway i vCloud kontrollpanelen kan man välja ”configure-services” och där under ligger en flik med namnet ”Load Balancer”. Här skapar vi en ny lastbalanserare som använder Round Robin balansering mot port 8085 på http och TCP (vår applikation avänder även socket.io via TCP). I nästa steg lägger vi till våra CoreOS noder med IP som vi konfigurerat för varje nod. När vi lagt till alla noder har vi följande uppsättning I lastbalanseraren. Sedan behöver vi ha en front som lyssnar på ett extern port och tar hand om alla requests, vilket vi lägger in under ”Virtual Servers” under ”Load Balancer” fliken. Här konfigurerar vi vårt externa organisations IP att lyssna på porten 8090 och att använda poolen ”CoreOS LB” som vi skapade tidigare. För att testa att vår lastbalanserare fungerar går vi mot vårt externa IP, I vårt fall 81.91.13.34 och mot porten vi valde (8090), http://81.91.13.34:8090. För en riktig lösning med webserver har man troligen port 80 och/eller 443 istället för port 8090. I vår applikation kan vi se inkommande TCP uppkopplingar som sker med socket.io från vår webläsare och kan där följa om det kommer in några till våra docker containers. Vi attachar oss till de containrar som körs för att se output till stdout från vår applikation. Som vi ser på bilden lastbalanseras inkommande connections till båda våra noder och Docker containrar vid omladdning av sidan. Allt fungerar som tänkt! Uppdatera Docker containern Tidigare definierade vi ExecStartPre I unit filen för våra fleet services. Där specifierade vi att docker skulle hämta hem senaste versionen av vår image. Det gör det möjligt att uppdatera vår image på ett enkelt sätt och sedan commita ändringar som vi pushar upp till vårt registry. När vi sedan startar om våra service:ar via fleet så hämtas den nya versionen av vår Docker image och startas på klustret. Summering Vi har nu satt upp ett HA kluster med självuppdaterande operativsystem vid namn CoreOS, använt isolerade LXC containrar via Docker och managerat vårt kluster med Fleet samt konfigurerat en lastbalanserare som nyttjar vårt kluster. Vårt spel har nu hög HA och skulle en instans krasha startas det om automatiskt och skulle en nod gå ner finns det andra som tar över. Skulle vi sedan sprida våra noder på andra ställen I världen eller på andra molntjänster/servrar skulle vi få än högre HA. En enkel skiss på hur klustret är uppbyggt: Att tänka på - DHCP stöds inte på ett bra sätt ännu av CoreOS - Fleet har hårdkodat användare för SSH vilken är ”core”. Vilket ställer till lite problem vid 'fleetctl ssh <machine_id>' om man har en annan användare. - SSH nycklar behöver kopieras och användas för att fleetctl ssh ska fungera. - eval `ssh-agent`; ssh-add behöver exekveras för att fleet ska kunna ssh:a med nycklar via en sshagent till noderna. - Vissa portar kan behöva öppnas upp i firewallen i vCloud. Länkar http://stable.release.core-os.net/amd64-usr/current/coreos_production_iso_image.iso http://www.coreos.com https://github.com/coreos/fleet https://github.com/coreos/etcd https://www.docker.com/ https://linuxcontainers.org/ https://github.com/coreos/coreos-cloudinit/blob/master/Documentation/config-drive.md
© Copyright 2024