Dawno, dawno temu, za górami, za lasami, gdzie kakałko trzeba było na palenisku robić odganiając się patykiem od niedźwiedzi (a przynajmniej ja tak pamiętam te czasy) a aplikacje webowe tworzyło się w jednej technologii, życie było o wiele prostsze. Obecnie są to kombajny złożone z wielu technologii. Szczególnie to widać na przykładzie front-endu, który rozrósł się niesamowicie od czasu stworzenia pierwszych wersji Node.js. Jesteśmy wprost bombardowani nowymi frameworkami SPA, które coraz prześcigają się w benchmarkach. Co to oznacza z perspektywy DevOps-a?… Problemy. Dla przykładu – obecnie tworze aplikację SPA na stack-u .NET Core MVC, Vue.js, Docker. Teraz, w jaki sposób, mogę użyć pipline w Gitlab CI aby poprawnie zbudował aplikację i uaktualnił obraz docker-a w repozytorium? Dla samego .NET Core-a jest dość mało obraz-ów, co dopiero przy potrzebie uruchamiania komend docker-a i npm-a?

W dzisiejszym artykule przedstawię wam, jak ja podszedłem do rozwiązania problemu 🙂

Faza I: Internecie, na pomoc!

Taka mała zagadka na początek: co robi programista jak natrafi na problem? Kakałko. Później? Śmieje się z basistów. Czemu? Bo to proste. A co robi odpowiedzialny programista?  A no idzie do wujka Google pytać o pomoc (potem kakałko). Dokładnie tak zrobiłem. Niestety tym razem się zawiodłem. Oczywiście na forum gitlab-a natrafiłem na wątki, w których ludzie opisywali podobne problemy, jednak tam zazwyczaj kończyło się tym, że w sekcji “before_script” ktoś sobie doinstalował czy to docker-a, czy brakujące jdk-a. Osobiście nie chciałem skończyć z plikiem gitlab-ci.yml wielkości wieży Eiffel -a.

Skoro wujek Google nie był mi w stanie pomóc, to może Docker Hub znajdzie mi obraz docker-a, który będzie zawierał wszystkie potrzebne mi elementy. No niestety tutaj też natrafiłem na ścianę. O ile są obrazy, które obsługują stack-a np. node + docker, to już .NET Core + Node + Docker nie znalazłem 🙁

Faza II: Plik wielkości wieży Eiffle

Chcą mieć różne technologie w pipeline Gitlab CI musiałem przetestować rozwiązanie, które było opisane w jednym z wątków na forum Gitlab-a, czyli bazowanie na jakimś podstawowym obrazie docker-a (który załatwia mi jedną funkcjonalność systemu), oraz doinstalowanie w sekcji “before_script” pozostałych zależności. Jako podstawę wybrałem ogólnie dostępny obraz o wdzięcznej nazwie “gitlab:dind”, który wykonał za mnie dość ciężką robotę w postaci udostępnienia mi api docker-a w pipeline Gitlab CI, który to już jest uruchomiony w Dockerze.

TODO: Wstawić jakiś śmieszny gif z podpisem dockercepcja

Tak, tak, dobrze przeczytaliście: otrzymałem  docker-a w dockerze. Stąd też skrót tego obrazu “dind” => “docker in docker”.

Sam obraz bazuje na systemie operacyjnym ubuntu w wersji 14.04 LTS. Jest to bardzo stabilna i nadal często spotykana dystrybucja Ubuntu na serwerach.

Instalacja zależności

Jako, że bazowy obraz jest stworzony na podstawie ubuntu, to do instalacji zależności wykorzystamy apt-get (system do zarządzania pakietami). Może to was zaskoczy, ale pierwszym pakietem, który musimy doinstalować, jest “curl”. Wszystko przez to, że wersja ubuntu udostępniona jako bazy obraz docker-a jest wykastrowana z wielu funkcjonalności aby zmniejszyć jej rozmiar.

Zainstalowanie najnowszej wersji curl-a wymaga uaktualnienia listy pakietów w repozytoriach. Oba polecenia możemy połączyć w jedno za pomocą operatora &&, którego wykorzystanie pozwoli nam na znaczne zmniejszenie ilości warstw w obrazach docker-a.

$ apt-get update && apt-get install -y curl

Następnie musimy zainstalować .NET Core-a. Tutaj nic odkrywczego, po prostu trzeba wejść na stronę https://www.microsoft.com/net/core#linuxubuntu i podążać zgodnie z instrukcją. Jednocześnie trzeba skasować wystąpienia słowa sudo, inaczej podczas budowania obrazu otrzymamy błąd.

$ curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
$ mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
$ sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-trusty-prod trusty main" > /etc/apt/sources.list.d/dotnetdev.list'
$ apt-get update && apt-get install -y dotnet-sdk-2.0.0

Potem przechodzimy do instalacji Node.js. Ponownie mogę tutaj dodać że nic odkrywczego się nie dzieje, wystarczy wejść w oficjalny tutorial instalacji znajdujący się w tym miejscu i wykonać wszystkie komendy.

$ curl -sL https://deb.nodesource.com/setup_8.x | bash -
$ apt-get update && apt-get install -y nodejs

Po przejściu tych wszystkich kroków plik gitlab-ci.yml powinien wyglądać podobnie do czegoś takiego:

stages:
  - build
build:
  image: 'gitlab:dind'
  stage: build
  before_script:
    - 'apt-get update && apt-get install -y curl'
    - 'curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg'
    - 'mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg'
    - sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-trusty-prod trusty main" > /etc/apt/sources.list.d/dotnetdev.list'
    - 'apt-get update && apt-get install -y dotnet-sdk-2.0.0'
    - 'curl -sL https://deb.nodesource.com/setup_8.x | bash -'
    - 'apt-get update && apt-get install -y nodejs'
    - 'dotnet restore'
  script: 
    - 'dotnet run'

Wstajemy na chwilę od biurka i w tym momencie tańczymy zasłużony taniec zwycięstwa. Nie martwcie się, też nie lubię się ruszać, stąd za taniec uważam przejście do lodówki po kawałek pizzy (ale w rytm muzyki).

Co z kodem? Jak widać zaczyna się tworzyć mały potworek, a mamy tutaj tylko jednego stage-a. Co jeśli byśmy musieli mieć np. 5 środowisk? Produkcja, preprodukcja, staging, QA, development? Zamknęlibyśmy to w jakąś funkcję? Albo po prostu powtarzali kod? No nie…

Faza III Własny obraz docker-a

Kiedy już mamy wszystkie potrzebne komendy do rozbudowania obrazu gitlab:dind toprosty sposób możemy stworzyć własny obraz, który będziemy mogli hostować za pomocą docker hub-a.

Wystarczy stworzyć plik Dockerfile i wypisać wszystkie potrzebne komendy poprzedzając je słowem “RUN”.

FROM gitlab/dind
RUN apt-get update && apt-get install -y curl

RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg

RUN mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
RUN sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-trusty-prod trusty main" > /etc/apt/sources.list.d/dotnetdev.list'

RUN apt-get update && apt-get install -y dotnet-sdk-2.0.0

RUN curl -sL https://deb.nodesource.com/setup_8.x | bash -

RUN apt-get install -y nodejs

Oczywiście, należy pamiętać, by nie pominąć komendy pobrania obrazu bazowego “FROM”.

Teraz, korzystając z docker CLI, umieszczamy stworzony obraz w repozytorium docker hub-a.

$ docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
$ docker build -t bd90/dotnet-core-node-docker .
$ docker push bd90/dotnet-core-node-docker

Rzecz jasna trzeba założyć konto na docker hubie (pamiętajcie, DOCKER hub, inny hub nie pyknie) 🙂

Dzięki takiemu zabiegowi możemy skrócić nasz plik gitlab-ci.yml do postaci:

stages:
  - build
build:
  image: 'bd90/dotnet-core-node-docker'
  stage: build
  before_script:
    - 'dotnet restore'
  script: 
    - 'dotnet run'

Ba! Teraz nawet, w prosty sposób, możemy użyć tego obrazu do innych stage-y, czy nawet projektów!

Podsumowanie

Teraz już wiecie jak można stworzyć pipeline-a Gitlab CI z różnymi technologiami w środku. Pamiętajcie, aby nie bać się tworzyć własnych obrazów docker-a. Dodatkowo, jeśli chcielibyście wykorzystać mój obraz, to znajdziecie go tutaj. W obecnym stanie ten obraz nie jest jeszcze gotowy do użycia produkcyjnego. Natomiast, jeśli macie propozycję na jego to dajcie znać w komentarzach, mailem lub na twitterze.

Do następnego!

Cześć