Pamiętacie te czasy, gdy deploy aplikacji równał się z otworzeniem klienta FTP-a czy SFTP-a i przekopiowaniu plików na serwer? Prawdę mówiąc, mimo że mamy już 2017 rok, to z większością moich aplikacji nadal tak robię. Są to małe twory leżące na pojedynczych serwerach. Niewielki nakład pracy powodował, że nie odczuwałem motywacji do przeniesienia ich w świat kontenerów.
Co w przypadku, kiedy nasza aplikacja jest już bardziej skomplikowana, wymaga dobrze skonfigurowanego środowiska, a dodatkowo ma być łatwa w odpaleniu na innych serwerach?
Hej przygodo - przygotuj się na poznanie Docker-a!
Docker to łatwe i przyjemne w obsłudze narzędzie do umieszczania rozproszonych aplikacji w wirtualnych kontenerach. Obsługuje go większość serwerów Linuxowych.
Instalacja Docker-a
Windows
Jeżeli macie zainstalowany package manager chocolatey
to do zainstalowania docker-a wystarczy wpisać pojedynczą komendę w konsoli:
C:\\> choco install docker
W przeciwnym wypadku instrukcje do zainstalowania chocolatey
macie tutaj.
MAC OS X
Dla systemu spod znaku jabłka polecam instalację za pomocą obrazu ściągniętego ze strony docker-a . Próbowałem go instalować za pomocą brew
, lecz nie działo to wtedy zbyt stabilnie (możliwe, że winę za to ponosiła instalacja wersji beta).
Linux
Opis jak zainstalować docker-a pod systemami operacyjnymi linux znajdziecie pod adresem https://docs.docker.com/engine/installation/linux/ubuntu/
Server Node.js
W chwili obecnej nie ma większego sensu skupiać się za bardzo na implementacji samego mikroserwisu. Jest to po prostu aplikacja serwująca "Hello World!" po wejściu na adres http://localhost:3000
const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(3000, () => console.log('Server is running on :3000'))
Wykorzystując framework https://expressjs.com/ możemy zmieścić się w 5 linijkach kodu. Do poprawnego działania musimy dodatkowo zainstalować paczkę experss
prze npm-a.
$ npm init $ npm i -D express
Następnie do pliku package.json
dodać nowe zadanie w sekcji scripts:
"start": "node index.js"
Konfiguracja Docker-a
Najpierw, aby dodać docker-a do projektu, musimy stworzyć pliki Dockerfile
i .dockerignore
$ touch Dockerfile $ touch .dockerignore
Następnie przechodzimy do uzupełnienia pliku .dockerignore
, który przypomina plik .gitignore
. W tym pliku wypisujemy wszystkie pliki / foldery, których nie chcemy umieścić w obrazie docker-a. Dla naszej przykładowej aplikacji stworzymy następująca zawartosć pliku:
node_modules npm-debug.log
Mając uzupełniony plik .dockerignore
możemy przejść do napisania treści pliku Dockerfile
. Jednak jeszcze przed "klepaniem" jeszcze jedna, bardzo ważna uwaga:
Każde polecenie w pliku tworzy nową warstwę w obrazie docker-a, dlatego warto utrzymywać ten plik tak krótki jak się tylko da.
Mając to na uwadze możemy przystąpić do tworzenia naszego obrazu. We wstępie musimy zdecydować, jaki obraz chcemy edytować. Mój wybór padł na node:boron
, który jest obrazem ostatniej wersji LTS Node-a. Użycie tego obrazu definiujemy za pomocą komendy:
FROM node:boron
Następnie tworzymy "workspace" dla aplikacji. W tym właśnie miejscu będziemy przetrzymywać wszystkie źródła.
RUN mkdir -p /user/src/app WORKDIR /usr/src/app
Dodatkowo dzięki poleceniu WORKDIR
ustawiliśmy naszą ścieżkę wykonywalną na stworzony wcześniej folder. Spowoduje to, że wszystkie polecenia jakie wywołamy będą odpalane w tej konkretnej lokacji.
Teraz musimy zająć się instalacją zależności naszej aplikacji. Potrzebujemy do tego pliku package.json.
COPY package.json /usr/src/app RUN npm install
Pewnie część z was zastanawia się, dlaczego w tym miejscu nie przekopiowałem całej aplikacji, pomimo tego że wcześniej pisałem o tym że każda komenda w pliku Dockerfile
tworzy nową warstwę w obrazie docker-a. Jest to pewnego rodzaju wyjątek wpływający na optymalizacje. Oddzielenie kopiowania pliku package.json
i instalacji zależności od kopiowania źródeł pozwala docker-owi na cache-owanie warstwy w której znajdują się wszystkie node_modules aż do momentu zmiany pliku package.json
. Skutkiem tego jest brak przymusu ponownego pobierania modułów z npm-a przy każdym budowaniu obrazu .
Teraz po kolei:
Przechodzimy do kopiowania źródeł aplikacji,
COPY . /usr/src/app
Zdefiniowania porta, na którym nasza aplikacja będzie nasłuchiwać wewnątrz kontenera
EXPOSE 3000
I wisienka na torcie: odpalenia naszej aplikacji za pomocą skryptu npm
CMD ["npm", "start"]
Po tych wszystkich zabiegach plik DockerFile
przedstawia się następująco:
FROM node:boron
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install
# Bundle app source
COPY . /usr/src/app
EXPOSE 3000
CMD [ "npm", "start" ]
To jest moment, w którym możemy przejść do zwieńczenia naszego dzieła i zbudowania naszego obrazu docker-a. Wykonamy to za pomocą jednego polecenia docker build
.
$ docker build -t bd90/node-microservice .
Polecam stosowanie flagi -t
w komendzie build
aby nadać prostą nazwę naszemu kontenerowi.
Jeśli wszystko zakończyło si ę bez błędów to po wpisaniu komendy:
$ docker images
Po wszystkim, co uczyniliśmy, powinniśmy otrzymać listę wszystkich naszych obrazów docker-a, a na niej obraz o wdzięcznej nazwie bd90/node-microservice
.
Żeby uruchomić kontener na naszym komputerze i zbindować jego port 3000 z portem 8000 musimy wywołać komendę docker run
z wykorzystanie flag -p
(ustawienia portu) i -d
(ustawieniem, który kontener ma zostać uruchomiony).
$ docker run -p 8000:3000 -d bd90/node-microservice
Po wykonaniu tej komendy nasza aplikacja powinna być dostępna pod adresem http://localhost:8000.
Na koniec jeden mały tip: Wszystkie aktywne kontenery możemy wypisać za pomocą polecenia docker ps.
$ docker ps
I to by było na tyle, jeśli chodzi o początek przygody z Docker-em i wykorzystaniem go do poskromienia procesu zarządzania mikroserwisem od strony DevOps-owej.
Mam nadzieje że komuś przyda się ten artykuł i do następnego :)