{"id":69351,"date":"2020-02-18T14:44:06","date_gmt":"2020-02-18T11:44:06","guid":{"rendered":"https:\/\/prohoster.info\/blog\/blog_prohoster\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker"},"modified":"2020-03-03T16:15:00","modified_gmt":"2020-03-03T13:15:00","slug":"sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","title":{"rendered":"Tworzenie CI\/CD i automatyzacja pracy z Dockerem","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Napisa\u0142am swoje pierwsze strony pod koniec lat 90. Wtedy by\u0142a to bardzo prosta sprawa. Mia\u0142am serwer Apache na jakim\u015b wsp\u00f3lnym hostingu, do kt\u00f3rego mo\u017cna by\u0142o si\u0119 dosta\u0107 przez FTP, wpisuj\u0105c w przegl\u0105dark\u0119 co\u015b takiego jak <code>ftp:\/\/ftp.example.com<\/code>. Potem trzeba by\u0142o wprowadzi\u0107 nazw\u0119 i has\u0142o, a nast\u0119pnie przes\u0142a\u0107 pliki na serwer. To by\u0142y inne czasy, wszystko wtedy by\u0142o prostsze ni\u017c teraz.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/ruvds\/blog\/488668\/\"><img decoding=\"async\" alt=\"Tworzenie CI\/CD i automatyzacja pracy z Dockerem\" src=\"\/wp-content\/uploads\/2020\/02\/0861cdd82770ef4695ffac7a09777c52.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex><\/p>\n<p>Przez te dwa dziesi\u0119ciolecia wiele si\u0119 zmieni\u0142o. Strony sta\u0142y si\u0119 bardziej z\u0142o\u017cone, przed wdro\u017ceniem trzeba je budowa\u0107. Jeden serwer przekszta\u0142ci\u0142 si\u0119 w wiele serwer\u00f3w dzia\u0142aj\u0105cych za r\u00f3wnowa\u017cnikami obci\u0105\u017cenia, a korzystanie z system\u00f3w kontroli wersji sta\u0142o si\u0119 powszechne.<\/p>\n<p>W przypadku mojego osobistego projektu mia\u0142am szczeg\u00f3ln\u0105 konfiguracj\u0119. Wiedzia\u0142am, \u017ce potrzebuj\u0119 mo\u017cliwo\u015bci wdro\u017cenia strony na produkcj\u0119 przy jednym dzia\u0142aniu: zapisywaniu kodu w ga\u0142\u0119zi <code>master<\/code> na GitHubie. Poza tym wiedzia\u0142am, \u017ce nie chc\u0119 zajmowa\u0107 si\u0119 zarz\u0105dzaniem ogromnym klastrem Kubernetes ani korzysta\u0107 z technologii Docker Swarm, ani utrzymywa\u0107 parku serwer\u00f3w z podami, agentami i innymi z\u0142o\u017cono\u015bciami. Aby osi\u0105gn\u0105\u0107 cel maksymalnego uproszczenia pracy, musia\u0142am zapozna\u0107 si\u0119 z CI\/CD.<\/p>\n<p>Je\u015bli masz ma\u0142y projekt (nasz przypadek dotyczy projektu Node.js) i chcesz dowiedzie\u0107 si\u0119, jak zautomatyzowa\u0107 wdro\u017cenie tego projektu, tak aby to, co jest w repozytorium, odpowiada\u0142o temu, co dzia\u0142a w produkcji, to s\u0105dz\u0119, \u017ce ta artyku\u0142 mo\u017ce Ci\u0119 zainteresowa\u0107.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Wymagania wst\u0119pne<\/h2>\n<p>\nOczekuje si\u0119, \u017ce czytelnik tego artyku\u0142u ma podstawow\u0105 wiedz\u0119 na temat pracy z wierszem polece\u0144 i pisania skrypt\u00f3w Bash. Ponadto b\u0119dzie potrzebowa\u0107 kont <noindex><a rel=\"nofollow\" href=\"https:\/\/travis-ci.org\/\">Travis CI<\/a><\/noindex> i <noindex><a rel=\"nofollow\" href=\"https:\/\/hub.docker.com\/\">Docker Hub<\/a><\/noindex>.<\/p>\n<h2>Cele<\/h2>\n<p>\nNie powiem, \u017ce ten artyku\u0142 mo\u017cna bezwarunkowo nazwa\u0107 \u201epodr\u0119cznikiem\u201d. To raczej dokument, w kt\u00f3rym opisuj\u0119, co si\u0119 nauczy\u0142am, i przedstawiam proces testowania i wdra\u017cania kodu w produkcji, realizowany w jednej zautomatyzowanej serii.<\/p>\n<p>Oto jak wygl\u0105da\u0142 m\u00f3j ko\u0144cowy proces pracy.<\/p>\n<p>Dla kodu wys\u0142anego do dowolnej ga\u0142\u0119zi repozytorium, z wyj\u0105tkiem <code>master<\/code>, wykonuje si\u0119 nast\u0119puj\u0105ce dzia\u0142ania:<\/p>\n<ul>\n<li>Uruchamiana jest budowa projektu na Travis CI.<\/li>\n<li>Wykonywane s\u0105 wszystkie testy jednostkowe, integracyjne i end-to-end.<\/li>\n<\/ul>\n<p>\nTylko dla kodu, kt\u00f3ry trafia do <code>master<\/code>, wykonuje si\u0119 nast\u0119puj\u0105ce:<\/p>\n<ul>\n<li>Wszystko, co powiedziano powy\u017cej, plus...<\/li>\n<li>Budowa obrazu Docker na podstawie aktualnego kodu, ustawie\u0144 i \u015brodowiska.<\/li>\n<li>Umieszczenie obrazu na Docker Hub.<\/li>\n<li>Pod\u0142\u0105czenie do serwera produkcyjnego.<\/li>\n<li>Pobranie obrazu z Docker Hub na serwer.<\/li>\n<li>Zatrzymanie bie\u017c\u0105cego kontenera i uruchomienie nowego, opartego na nowym obrazie.<\/li>\n<\/ul>\n<p>\nJe\u015bli nie wiesz nic o Dockerze, obrazach i kontenerach \u2014 nie martw si\u0119. Wszystkiego ci o tym opowiem.<\/p>\n<h2>Czym jest CI\/CD?<\/h2>\n<p>\nSkr\u00f3t CI\/CD oznacza \u201econtinuous integration\/continuous deployment\u201d \u2014 \u201eci\u0105g\u0142a integracja\/ci\u0105g\u0142e wdra\u017canie\u201d.<\/p>\n<h3>\u258dCi\u0105g\u0142a integracja<\/h3>\n<p>\nCi\u0105g\u0142a integracja to proces, podczas kt\u00f3rego programi\u015bci dokonuj\u0105 commit\u00f3w w g\u0142\u00f3wnym repozytorium kodu \u017ar\u00f3d\u0142owego projektu (zwykle w ga\u0142\u0119zi <code>master<\/code>). Jako\u015b\u0107 kodu jest zapewniana poprzez przeprowadzanie automatyzowanych test\u00f3w.<\/p>\n<h3>\u258dCi\u0105g\u0142e wdra\u017canie<\/h3>\n<p>\nCi\u0105g\u0142e wdra\u017canie to cz\u0119ste automatyzowane wdra\u017canie kodu na produkcji. Druga cz\u0119\u015b\u0107 skr\u00f3tu CI\/CD czasami rozwija si\u0119 jako \u201econtinuous delivery\u201d (\u201eci\u0105g\u0142a dostawa\u201d). To w zasadzie to samo, co \u201eci\u0105g\u0142e wdra\u017canie\u201d, ale \u201eci\u0105g\u0142a dostawa\u201d oznacza konieczno\u015b\u0107 r\u0119cznego potwierdzenia zmian przed rozpocz\u0119ciem procesu wdra\u017cania projektu.<\/p>\n<h2>Rozpocz\u0119cie pracy<\/h2>\n<p>\nAplikacja, na kt\u00f3rej to wszystko opanowa\u0142am, nazywa si\u0119 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/taniarascia\/takenote\">TakeNote<\/a><\/noindex>. To projekt internetowy, nad kt\u00f3rym pracuj\u0119, przeznaczony do robienia notatek. Na pocz\u0105tku pr\u00f3bowa\u0142am zrobi\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/jamstack.org\/\">projekt JAMStack<\/a><\/noindex>-projekt lub tylko aplikacj\u0119 frontendow\u0105 bez serwera, aby skorzysta\u0107 z standardowych mo\u017cliwo\u015bci hostingu i wdra\u017cania projekt\u00f3w, kt\u00f3re oferuje <noindex><a rel=\"nofollow\" href=\"https:\/\/www.netlify.com\/\">Netlify<\/a><\/noindex>. W miar\u0119 jak ros\u0142a z\u0142o\u017cono\u015b\u0107 aplikacji, potrzebowa\u0142am stworzy\u0107 i jej cz\u0119\u015b\u0107 serwerow\u0105, co oznacza\u0142o, \u017ce musia\u0142am opracowa\u0107 w\u0142asn\u0105 strategi\u0119 automatycznej integracji i automatycznego wdra\u017cania projektu.<\/p>\n<p>W moim przypadku aplikacja to serwer Express dzia\u0142aj\u0105cy w \u015brodowisku Node.js, obs\u0142uguj\u0105cy jednoplakowym aplikacj\u0119 React i wspieraj\u0105cy zabezpieczone API serwerowe. Ta architektura pod\u0105\u017ca za strategi\u0105, kt\u00f3r\u0105 mo\u017cna znale\u017a\u0107 w <noindex><a rel=\"nofollow\" href=\"https:\/\/www.taniarascia.com\/full-stack-cookies-localstorage-react-express\/\">tym<\/a><\/noindex> przewodniku po pe\u0142nostekowej autoryzacji.<\/p>\n<p>Skonsultowa\u0142am si\u0119 z <noindex><a rel=\"nofollow\" href=\"http:\/\/qualitytesting.tech\/\">przestrzeni nazw z<\/a><\/noindex>, kt\u00f3ry jest ekspertem w automatyzacji, i zapyta\u0142em go, co musz\u0119 zrobi\u0107, aby wszystko dzia\u0142a\u0142o tak, jak potrzebuj\u0119. Podsun\u0105\u0142 mi pomys\u0142 na to, jak powinien wygl\u0105da\u0107 zautomatyzowany proces pracy, opisany w sekcji \u00abCele\u00bb tego artyku\u0142u. To, \u017ce wyznaczy\u0142em sobie takie cele, oznacza\u0142o, \u017ce musz\u0119 zrozumie\u0107, jak u\u017cywa\u0107 Dockera.<\/p>\n<h2>Docker<\/h2>\n<p>\nDocker to narz\u0119dzie, kt\u00f3re dzi\u0119ki technologii konteneryzacji u\u0142atwia dystrybucj\u0119 aplikacji oraz ich wdra\u017canie i uruchamianie w tym samym \u015brodowisku, nawet je\u015bli sama platforma Docker dzia\u0142a w r\u00f3\u017cnych \u015brodowiskach. Na pocz\u0105tek musia\u0142em zdoby\u0107 narz\u0119dzia interfejsu wiersza polece\u0144 (CLI) Dockera. <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/install\/\">Instrukcja<\/a><\/noindex> instalacji Dockera nie jest bardzo jasna i zrozumia\u0142a, ale mo\u017cna z niej dowiedzie\u0107 si\u0119, \u017ce aby wykona\u0107 pierwszy krok instalacji, nale\u017cy pobra\u0107 Docker Desktop (na Mac lub Windows).<\/p>\n<p>Docker Hub to mniej wi\u0119cej to samo, co <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/\">GitHub<\/a><\/noindex> dla repozytori\u00f3w git lub rejestr <noindex><a rel=\"nofollow\" href=\"https:\/\/www.npmjs.com\/\">npm<\/a><\/noindex> dla pakiet\u00f3w JavaScript. To internetowe repozytorium dla obraz\u00f3w Dockera. To w\u0142a\u015bnie do niego \u0142\u0105czy si\u0119 Docker Desktop.<\/p>\n<p>Zatem, aby rozpocz\u0105\u0107 prac\u0119 z Dockerem, musisz zrobi\u0107 dwie rzeczy:<\/p>\n<ul>\n<li>Zainstaluj <noindex><a rel=\"nofollow\" href=\"https:\/\/www.docker.com\/get-started\">Docker Desktop<\/a><\/noindex>.<\/li>\n<li>Zarejestruj si\u0119 na <noindex><a rel=\"nofollow\" href=\"https:\/\/hub.docker.com\/\">Docker Hub<\/a><\/noindex>.<\/li>\n<\/ul>\n<p>\nPo tym mo\u017cesz sprawdzi\u0107 dzia\u0142anie Docker CLI, wykonuj\u0105c nast\u0119puj\u0105ce polecenie, aby sprawdzi\u0107 wersj\u0119 Dockera:<\/p>\n<pre><code class=\"plaintext\">docker -v<\/code><\/pre>\n<p>\nNast\u0119pnie zaloguj si\u0119 do Docker Huba, wpisuj\u0105c, gdy zostaniesz o to poproszony, swoj\u0105 nazw\u0119 u\u017cytkownika i has\u0142o:<\/p>\n<pre><code class=\"plaintext\">docker login<\/code><\/pre>\n<p>\nAby korzysta\u0107 z Dockera, musisz zrozumie\u0107 koncepcje obraz\u00f3w i kontener\u00f3w.<\/p>\n<h3>\u258dObrazy<\/h3>\n<p>\nObraz to co\u015b w rodzaju planu, zawieraj\u0105cego instrukcje dotycz\u0105ce budowy kontenera. To niezmienna kopia systemu plik\u00f3w i ustawie\u0144 aplikacji. Programi\u015bci mog\u0105 \u0142atwo wymienia\u0107 si\u0119 obrazami.<\/p>\n<pre><code class=\"plaintext\"># \u0412\u044b\u0432\u043e\u0434 \u0441\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u043e\u0431\u043e \u0432\u0441\u0435\u0445 \u043e\u0431\u0440\u0430\u0437\u0430\u0445\ndocker images<\/code><\/pre>\n<p>\nTo polecenie wy\u015bwietli tabel\u0119 z nast\u0119puj\u0105cym nag\u0142\u00f3wkiem:<\/p>\n<pre><code class=\"plaintext\">REPOSITORY \u00a0 \u00a0 TAG     IMAGE ID \u00a0 \u00a0 CREATED     SIZE\n---<\/code><\/pre>\n<p>\nNast\u0119pnie przeanalizujemy kilka przyk\u0142ad\u00f3w polece\u0144 w takim samym formacie \u2014 najpierw polecenie z komentarzem, a potem przyk\u0142ad tego, co mo\u017ce zwr\u00f3ci\u0107.<\/p>\n<h3>\u258dKontenery<\/h3>\n<p>\nKontener to pakiet wykonawczy, kt\u00f3ry zawiera wszystko, co potrzebne do uruchomienia aplikacji. Dzi\u0119ki temu podej\u015bciu aplikacja zawsze b\u0119dzie dzia\u0142a\u0107 w ten sam spos\u00f3b, niezale\u017cnie od infrastruktury: w izolowanym \u015brodowisku i w tym samym \u015brodowisku. M\u00f3wi si\u0119 o tym, \u017ce w r\u00f3\u017cnych \u015brodowiskach uruchamiane s\u0105 instancje tego samego obrazu.<\/p>\n<pre><code class=\"plaintext\"># \u041f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432\ndocker ps -a\nCONTAINER ID \u00a0 \u00a0 IMAGE     COMMAND \u00a0 \u00a0 CREATED     STATUS     PORTS \u00a0 \u00a0 NAMES\n---<\/code><\/pre>\n<p><\/p>\n<h3>\u258dTagi<\/h3>\n<p>\nTag to wskazanie konkretnej wersji obrazu.<\/p>\n<h3>\u258dKr\u00f3tka informacja o poleceniach Docker<\/h3>\n<p>\nOto przegl\u0105d niekt\u00f3rych cz\u0119sto u\u017cywanych polece\u0144 Docker.<\/p>\n<p>Zesp\u00f3\u0142<\/p>\n<p>Kontekst<\/p>\n<p>Akcja<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/build\/\">docker build<\/a><\/noindex><\/p>\n<p>Obraz<\/p>\n<p>Budowanie obrazu z Dockerfile<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/tag\/\">docker tag<\/a><\/noindex><\/p>\n<p>Obraz<\/p>\n<p>Oznaczanie obrazu<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/images\/\">docker images<\/a><\/noindex><\/p>\n<p>Obraz<\/p>\n<p>Wy\u015bwietlanie listy obraz\u00f3w<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/run\/\">docker run<\/a><\/noindex><\/p>\n<p>Kontener<\/p>\n<p>Uruchamianie kontenera na podstawie obrazu<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/push\/\">docker push<\/a><\/noindex><\/p>\n<p>Obraz<\/p>\n<p>Wysy\u0142anie obrazu do rejestru<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"http:\/\/about:blank\">docker pull<\/a><\/noindex><\/p>\n<p>Obraz<\/p>\n<p>Pobieranie obrazu z rejestru<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/ps\/\">docker ps<\/a><\/noindex><\/p>\n<p>Kontener<\/p>\n<p>Wy\u015bwietlanie listy kontener\u00f3w<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/system_prune\/\">docker system prune<\/a><\/noindex><\/p>\n<p>Obraz\/Kontener<\/p>\n<p>Usuwanie nieu\u017cywanych kontener\u00f3w i obraz\u00f3w<\/p>\n<h3>\u258dPlik Dockerfile<\/h3>\n<p>\nWiem, jak lokalnie uruchomi\u0107 aplikacj\u0119 do produkcji. Mam konfiguracj\u0119 Webpack, przeznaczon\u0105 do budowy gotowej aplikacji React. Nast\u0119pnie mam polecenie uruchamiaj\u0105ce serwer oparty na Node.js, na porcie <code>5000<\/code>. Wygl\u0105da to tak:<\/p>\n<pre><code class=\"plaintext\">npm i \u00a0 \u00a0 \u00a0 \u00a0 # instalacja zale\u017cno\u015bci\nnpm run build # budowanie aplikacji React\nnpm run start # uruchamianie serwera Node<\/code><\/pre>\n<p>\nNale\u017cy zauwa\u017cy\u0107, \u017ce nie mam aplikacji przyk\u0142adowej na ten materia\u0142. Jednak tutaj, do eksperyment\u00f3w, nada si\u0119 ka\u017cda prosta aplikacja Node.<\/p>\n<p>Aby skorzysta\u0107 z kontenera, musisz da\u0107 instrukcje Docker. Robi si\u0119 to za pomoc\u0105 pliku, kt\u00f3ry nazywa si\u0119 <code>Dockerfile<\/code>, znajduj\u0105cego si\u0119 w katalogu g\u0142\u00f3wnym projektu. Ten plik na pocz\u0105tku wydaje si\u0119 do\u015b\u0107 niezrozumia\u0142y.<\/p>\n<p>Ale to, co si\u0119 w nim znajduje, jedynie opisuje, za pomoc\u0105 specjalnych polece\u0144, co\u015b podobnego do konfiguracji \u015brodowiska roboczego. Oto niekt\u00f3re z tych polece\u0144:<\/p>\n<ul>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#from\">Z FROM<\/a><\/noindex> \u2014 To polecenie rozpoczyna plik. Okre\u015bla bazowy obraz, na podstawie kt\u00f3rego budowany jest kontener.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#copy\">COPY<\/a><\/noindex> \u2014 Kopiowanie plik\u00f3w z lokalnego \u017ar\u00f3d\u0142a do kontenera.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#workdir\">WORKDIR<\/a><\/noindex> \u2014 Ustawienie katalogu roboczego dla nast\u0119pnych polece\u0144.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#run\">RUN<\/a><\/noindex> \u2014 Uruchamianie polece\u0144.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#expose\">EXPOSE<\/a><\/noindex> \u2014 Konfiguracja portu.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#entrypoint\">PUNKCIE WEJ\u015aCIA<\/a><\/noindex> \u2014 Wskazanie polecenia do wykonania.<\/li>\n<\/ul>\n<p>\n<code>Dockerfile<\/code> mo\u017ce wygl\u0105da\u0107 mniej wi\u0119cej tak:<\/p>\n<pre><code class=\"plaintext\"># \u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043e\u0431\u0440\u0430\u0437\nFROM node:12-alpine\n\n# \u0421\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u0438\u0437 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e app\/\nCOPY . app\/\n\n# \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c app\/ \u0432 \u0440\u043e\u043b\u0438 \u0440\u0430\u0431\u043e\u0447\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438\nWORKDIR app\/\n\n# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 (\u043a\u043e\u043c\u0430\u043d\u0434\u0430 npm ci \u043f\u043e\u0445\u043e\u0436\u0430 npm i, \u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0441\u0431\u043e\u0440\u043e\u043a)\nRUN npm ci --only-production\n\n# \u0421\u043e\u0431\u0440\u0430\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 React-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d\u0430\nRUN npm run build\n\n# \u041f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0440\u0442\nEXPOSE 5000\n\n# \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c Node-\u0441\u0435\u0440\u0432\u0435\u0440\nENTRYPOINT npm run start<\/code><\/pre>\n<p>\nW zale\u017cno\u015bci od wybranego bazowego obrazu, mo\u017ce by\u0107 konieczne zainstalowanie dodatkowych zale\u017cno\u015bci. Niekt\u00f3re obrazy bazowe (takie jak Node Alpine Linux) zosta\u0142y zaprojektowane, aby by\u0142y jak najbardziej kompaktowe. W zwi\u0105zku z tym mog\u0105 nie zawiera\u0107 niekt\u00f3rych program\u00f3w, na kt\u00f3re liczysz.<\/p>\n<h3>\u258dBudowanie, tagowanie i uruchamianie kontenera<\/h3>\n<p>\nLokalne budowanie i uruchamianie kontenera \u2014 to, po tym jak mamy <code>Dockerfile<\/code>, zadania s\u0105 do\u015b\u0107 proste. Zanim wy\u015blesz obraz na Docker Hub, nale\u017cy go przetestowa\u0107 lokalnie.<\/p>\n<h3>\u258dBudowanie<\/h3>\n<p>\nNajpierw musisz zbudowa\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/build\/\">obraz<\/a><\/noindex>, podaj\u0105c nazw\u0119 oraz, co nie jest obowi\u0105zkowe, tag (je\u015bli tag nie zostanie podany, system automatycznie przypisze obrazowi tag <code>latest<\/code>).<\/p>\n<pre><code class=\"plaintext\"># \u0421\u0431\u043e\u0440\u043a\u0430 \u043e\u0431\u0440\u0430\u0437\u0430\ndocker build -t &lt;image&gt;:&lt;tag&gt; .<\/code><\/pre>\n<p>\nPo wykonaniu tej komendy mo\u017cna obserwowa\u0107, jak Docker wykonuje budow\u0119 obrazu.<\/p>\n<pre><code class=\"plaintext\">Wysy\u0142anie kontekstu budowy do demona Dockera \u00a0 2.88MB\nKrok 1\/9 : FROM node:12-alpine\n\u00a0---&gt; ...wykonywanie etap\u00f3w budowy...\nPomy\u015blnie zbudowano 123456789123\nPomy\u015blnie otagowano &lt;obraz&gt;:&lt;tag&gt;<\/code><\/pre>\n<p>\nBudowa mo\u017ce zaj\u0105\u0107 kilka minut \u2014 wszystko zale\u017cy od tego, ile masz zale\u017cno\u015bci. Po zako\u0144czeniu budowy mo\u017cesz wykona\u0107 komend\u0119 <code>docker images<\/code> i spojrze\u0107 na opis swojego nowego obrazu.<\/p>\n<pre><code class=\"plaintext\">REPOZYTORIUM\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 TAG               ID OBRAZU\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 UTWORZONY              ROZMIAR\n&lt;obraz&gt; \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 latest            123456789123\u00a0 \u00a0 \u00a0 \u00a0 Oko\u0142o minuty temu   x.xxGB<\/code><\/pre>\n<p><\/p>\n<h3>\u258dUruchamianie<\/h3>\n<p>\nObraz zosta\u0142 utworzony. Oznacza to, \u017ce na jego podstawie mo\u017cna uruchomi\u0107 kontener. Poniewa\u017c chc\u0119 mie\u0107 mo\u017cliwo\u015b\u0107 dost\u0119pu do aplikacji dzia\u0142aj\u0105cej w kontenerze pod adresem <code>localhost:5000<\/code>, w lewej cz\u0119\u015bci pary <code>5000:5000<\/code> w nast\u0119pnej komendzie ustawi\u0142am <code>5000<\/code>. W prawej cz\u0119\u015bci znajduje si\u0119 port kontenera.<\/p>\n<pre><code class=\"plaintext\"># \u0417\u0430\u043f\u0443\u0441\u043a \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u0440\u0442\u0430 5000 \u0438 \u043f\u043e\u0440\u0442\u0430 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 5000\ndocker run -p 5000:5000 &lt;image&gt;:&lt;tag&gt;<\/code><\/pre>\n<p>\nTeraz, gdy kontener zosta\u0142 utworzony i uruchomiony, mo\u017cesz skorzysta\u0107 z komendy <code>docker ps<\/code> aby zobaczy\u0107 informacje o tym kontenerze (lub mo\u017cesz u\u017cy\u0107 komendy <code>docker ps -a<\/code>, kt\u00f3ra wy\u015bwietla informacje o wszystkich kontenerach, a nie tylko o dzia\u0142aj\u0105cych).<\/p>\n<pre><code class=\"plaintext\">ID KONTENERA        OBRAZ               KOMENDA                  UTWORZONY              STATUS                   PORTY                     NAZWY\n987654321234                     \"\/bin\/sh -c 'npm run...\"    6 sekund temu        Dzia\u0142a 6 sekund            0.0.0.0:5000-&gt;5000\/tcp   stoic_darwin<\/code><\/pre>\n<p>\nJe\u015bli teraz przejdziesz pod adres <code>localhost:5000<\/code> \u2014 mo\u017cesz zobaczy\u0107 stron\u0119 dzia\u0142aj\u0105cej aplikacji, kt\u00f3ra wygl\u0105da dok\u0142adnie tak samo jak strona aplikacji dzia\u0142aj\u0105cej w \u015brodowisku produkcyjnym.<\/p>\n<h3>\u258dPrzypisanie tagu i publikacja<\/h3>\n<p>\nAby skorzysta\u0107 z jednego z utworzonych obraz\u00f3w na serwerze produkcyjnym, musimy mie\u0107 mo\u017cliwo\u015b\u0107 za\u0142adowania tego obrazu z Docker Hub. Oznacza to, \u017ce najpierw musimy stworzy\u0107 repozytorium na Docker Hub dla projektu. Nast\u0119pnie b\u0119dziemy mieli miejsce, gdzie mo\u017cemy wys\u0142a\u0107 obraz. Obraz trzeba nazwa\u0107 tak, aby jego nazwa zaczyna\u0142a si\u0119 od naszej nazwy u\u017cytkownika na Docker Hub. Nast\u0119pnie powinno si\u0119 pojawi\u0107 nazwa repozytorium. Na ko\u0144cu nazwy mo\u017ce znajdowa\u0107 si\u0119 dowolny tag. Poni\u017cej przedstawiono przyk\u0142ad nazewnictwa obraz\u00f3w wed\u0142ug tej zasady.<\/p>\n<p>Teraz mo\u017cna zbudowa\u0107 obraz, nadaj\u0105c mu now\u0105 nazw\u0119 i wykona\u0107 polecenie <code>docker push<\/code> aby wys\u0142a\u0107 go do repozytorium Docker Hub.<\/p>\n<pre><code class=\"plaintext\">docker build -t  \/ : .\ndocker tag  \/ :  \/ :latest\ndocker push  \/ :\n\n# W praktyce mo\u017ce to wygl\u0105da\u0107 na przyk\u0142ad tak:\ndocker build -t user \/ app:v1.0.0 .\ndocker tag user \/ app:v1.0.0 user \/ app:latest\ndocker push user \/ app:v1.0.0<\/code><\/pre>\n<p>\nJe\u015bli wszystko p\u00f3jdzie zgodnie z planem, obraz b\u0119dzie dost\u0119pny na Docker Hub i b\u0119dzie mo\u017cna go \u0142atwo za\u0142adowa\u0107 na serwer lub przekaza\u0107 innym deweloperom.<\/p>\n<h2>Nast\u0119pne kroki<\/h2>\n<p>\nDo tej pory upewnili\u015bmy si\u0119, \u017ce aplikacja dzia\u0142aj\u0105ca w kontenerze Docker dzia\u0142a lokalnie. Za\u0142adowali\u015bmy kontener na Docker Hub. Wszystko to oznacza, \u017ce ju\u017c naprawd\u0119 bardzo dobrze posun\u0119li\u015bmy si\u0119 do celu. Teraz musimy rozwi\u0105za\u0107 jeszcze dwa kwestie:<\/p>\n<ul>\n<li>Konfiguracja narz\u0119dzia CI do testowania i wdra\u017cania kodu.<\/li>\n<li>Konfiguracja serwera produkcyjnego, aby m\u00f3g\u0142 \u0142adowa\u0107 i uruchamia\u0107 nasz kod.<\/li>\n<\/ul>\n<p>\nW naszym przypadku jako rozwi\u0105zanie CI\/CD u\u017cywane jest <noindex><a rel=\"nofollow\" href=\"https:\/\/travis-ci.org\/\">Travis CI<\/a><\/noindex>. Jako serwer \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/www.digitalocean.com\/\">DigitalOcean<\/a><\/noindex>.<\/p>\n<p>Nale\u017cy zauwa\u017cy\u0107, \u017ce mo\u017cna r\u00f3wnie\u017c skorzysta\u0107 z innej kombinacji us\u0142ug. Na przyk\u0142ad, zamiast Travis CI mo\u017cna u\u017cy\u0107 CircleCI lub Github Actions. A zamiast DigitalOcean \u2014 AWS lub Linode.<\/p>\n<p>Postanowili\u015bmy pracowa\u0107 z Travis CI, a w tym serwisie mam ju\u017c co\u015b skonfigurowane. Dlatego teraz kr\u00f3tko opowiem o tym, jak go przygotowa\u0107 do pracy.<\/p>\n<h2>Travis CI<\/h2>\n<p>\nTravis CI to narz\u0119dzie do testowania i wdra\u017cania kodu. Nie chcia\u0142bym wchodzi\u0107 w szczeg\u00f3\u0142y konfiguracji Travis CI, poniewa\u017c ka\u017cdy projekt jest unikalny i nie przyniesie to szczeg\u00f3lnej korzy\u015bci. Ale opowiem o podstawach, kt\u00f3re pozwol\u0105 wam zacz\u0105\u0107 prac\u0119 w przypadku, gdy zdecydujecie si\u0119 korzysta\u0107 z Travis CI. Cokolwiek wybierzecie \u2014 Travis CI, CircleCI, Jenkins czy co\u015b innego, wsz\u0119dzie b\u0119d\u0105 mia\u0142y zastosowanie podobne metody konfiguracji.<\/p>\n<p>Aby rozpocz\u0105\u0107 prac\u0119 z Travis CI, przejd\u017a do <noindex><a rel=\"nofollow\" href=\"https:\/\/travis-ci.org\/\">stron\u0119 projektu<\/a><\/noindex> i za\u0142\u00f3\u017c konto. Nast\u0119pnie zintegruj Travis CI ze swoim kontem GitHub. Podczas konfiguracji systemu b\u0119dziesz musia\u0142 wskaza\u0107 repozytorium, kt\u00f3rego automatyzacj\u0105 chcesz si\u0119 zaj\u0105\u0107, oraz nada\u0107 dost\u0119p do niego. (Korzystam z GitHub, ale jestem pewna, \u017ce Travis CI mo\u017ce r\u00f3wnie\u017c integrowa\u0107 si\u0119 z BitBucket, GitLab i innymi podobnymi us\u0142ugami).<\/p>\n<p>Za ka\u017cdym razem, gdy Travis CI zaczyna prac\u0119, uruchamiany jest serwer, kt\u00f3ry wykonuje polecenia okre\u015blone w pliku konfiguracyjnym, w tym wdra\u017canie odpowiednich ga\u0142\u0119zi repozytorium.<\/p>\n<h3>\u258dCykl \u017cycia zadania<\/h3>\n<p>\nPlik konfiguracyjny Travis CI, nazywany <code>.travis.yml<\/code> i przechowywany w g\u0142\u00f3wnym katalogu projektu, wspiera koncepcj\u0119 zdarze\u0144 <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.travis-ci.com\/user\/job-lifecycle\/\">cykl \u017cycia<\/a><\/noindex> zadania. Oto te zdarzenia, przedstawione w kolejno\u015bci, w jakiej wyst\u0119puj\u0105:<\/p>\n<ul>\n<li><code>apt addons<\/code><\/li>\n<li><code>cache components<\/code><\/li>\n<li><code>before_install<\/code><\/li>\n<li><code>install<\/code><\/li>\n<li><code>before_script<\/code><\/li>\n<li><code>script<\/code><\/li>\n<li><code>before_cache<\/code><\/li>\n<li><code>after_success lub after_failure<\/code><\/li>\n<li><code>before_deploy<\/code><\/li>\n<li><code>deploy<\/code><\/li>\n<li><code>after_deploy<\/code><\/li>\n<li><code>after_script<\/code><\/li>\n<\/ul>\n<p><\/p>\n<h3>\u258dTestowanie<\/h3>\n<p>\nW pliku konfiguracyjnym zamierzam skonfigurowa\u0107 lokalny serwer Travis CI. Wybra\u0142am wersj\u0119 Node 12 jako j\u0119zyk i poinformowa\u0142am system, \u017ce musi zainstalowa\u0107 zale\u017cno\u015bci potrzebne do u\u017cycia Dockera.<\/p>\n<p>Wszystko, co wymieniono w <code>.travis.yml<\/code>, zostanie wykonane przy wykonywaniu wszystkich pull request\u00f3w do wszystkich ga\u0142\u0119zi repozytorium, chyba \u017ce zaznaczone jest inaczej. To przydatna funkcja, poniewa\u017c oznacza, \u017ce mo\u017cemy testowa\u0107 ca\u0142y kod, kt\u00f3ry trafia do repozytorium. Pozwala to sprawdzi\u0107, czy kod jest gotowy do zapisania w ga\u0142\u0119zi <code>master<\/code>, i nie zak\u0142\u00f3ci on procesu budowania projektu. W tej globalnej konfiguracji wszystko instaluj\u0119 lokalnie, uruchamiam serwer dewelopera Webpack w tle (to jest cecha mojego \u015brodowiska pracy) i wykonuj\u0119 testy.<\/p>\n<p>Je\u015bli chcesz, aby w Twoim repozytorium pojawia\u0142y si\u0119 insignie z informacjami o pokryciu kodu testami, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.taniarascia.com\/display-build-status-and-test-coverage\/\">tutaj<\/a><\/noindex> mo\u017cesz znale\u017a\u0107 kr\u00f3tki przewodnik po u\u017cyciu Jest, Travis CI i Coveralls do zbierania i wy\u015bwietlania tych informacji.<\/p>\n<p>Oto zawarto\u015b\u0107 pliku <code>.travis.yml<\/code>:<\/p>\n<pre><code class=\"plaintext\"># \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u044f\u0437\u044b\u043a\nlanguage: node_js\n\n# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u044e Node.js\nnode_js:\n\u00a0\u00a0- '12'\n\nservices:\n\u00a0\u00a0# \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 Docker\n\u00a0\u00a0- docker\n\ninstall:\n\u00a0\u00a0# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\n\u00a0\u00a0- npm ci\n\nbefore_script:\n\u00a0\u00a0# \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\n\u00a0\u00a0- npm run dev &amp;\n\nscript:\n\u00a0\u00a0# \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u044b\n\u00a0\u00a0- npm run test<\/code><\/pre>\n<p>\nNa tym ko\u0144cz\u0105 si\u0119 te dzia\u0142ania, kt\u00f3re s\u0105 wykonywane dla wszystkich ga\u0142\u0119zi repozytorium i dla pull request\u00f3w.<\/p>\n<h3>\u258dWdra\u017canie<\/h3>\n<p>\nZak\u0142adaj\u0105c, \u017ce wszystkie zautomatyzowane testy zako\u0144czy\u0142y si\u0119 pomy\u015blnie, mo\u017cemy, co nie jest konieczne, wdro\u017cy\u0107 kod na serwerze produkcyjnym. Poniewa\u017c chcemy robi\u0107 to tylko dla kodu z ga\u0142\u0119zi <code>master<\/code>, wydajemy odpowiednie polecenia systemowi w ustawieniach wdro\u017cenia. Zanim spr\u00f3bujesz u\u017cy\u0107 w swoim projekcie kodu, kt\u00f3ry om\u00f3wimy dalej, chcia\u0142abym Ci\u0119 ostrzec, \u017ce musisz mie\u0107 rzeczywisty skrypt wywo\u0142ywany do wdro\u017cenia.<\/p>\n<pre><code class=\"plaintext\">deploy:\n  # Budowanie kontenera Docker i wysy\u0142anie go na Docker Hub\n  provider: script\n  script: bash deploy.sh\n  on:\n    branch: master<\/code><\/pre>\n<p>\nSkrypt wdro\u017ceniowy realizuje dwa zadania:<\/p>\n<ul>\n<li>Budowanie, tagowanie i przesy\u0142anie obrazu na Docker Hub za pomoc\u0105 narz\u0119dzia CI (w naszym przypadku jest to Travis CI).<\/li>\n<li>Pobranie obrazu na serwer, zatrzymanie starego kontenera i uruchomienie nowego (w naszym przypadku serwer dzia\u0142a na platformie DigitalOcean).<\/li>\n<\/ul>\n<p>\nNajpierw musisz skonfigurowa\u0107 automatyczny proces budowania, tagowania i przesy\u0142ania obrazu na Docker Hub. Wszystko to wygl\u0105da bardzo podobnie do tego, co ju\u017c robili\u015bmy r\u0119cznie, z wyj\u0105tkiem tego, \u017ce tutaj potrzebujemy strategii nadawania obrazom unikalnych tag\u00f3w oraz automatyzacji logowania. Mia\u0142am trudno\u015bci z niekt\u00f3rymi szczeg\u00f3\u0142ami skryptu wdro\u017cenia, jak strategia tagowania, logowanie, kodowanie kluczy SSH, ustanawianie po\u0142\u0105czenia SSH. Na szcz\u0119\u015bcie, m\u00f3j ch\u0142opak doskonale radzi sobie z bashem, jak i wieloma innymi rzeczami. Pom\u00f3g\u0142 mi napisa\u0107 ten skrypt.<\/p>\n<p>Zatem pierwsza cz\u0119\u015b\u0107 skryptu to wysy\u0142anie obrazu na Docker Hub. Zrobienie tego jest do\u015b\u0107 proste. U\u017cyty przeze mnie schemat tagowania polega na \u0142\u0105czeniu hasha git z tagiem git, je\u015bli taki istnieje. To pozwala na stworzenie unikalnego tagu i u\u0142atwia identyfikacj\u0119 budowy, na kt\u00f3rej jest oparty. <code>DOCKER_USERNAME<\/code> i <code>DOCKER_PASSWORD<\/code> \u2014 to zmienne \u015brodowiskowe u\u017cytkownika, kt\u00f3re mo\u017cna ustawi\u0107 za pomoc\u0105 interfejsu Travis CI. Travis CI automatycznie zajmie si\u0119 danymi wra\u017cliwymi, aby nie wpad\u0142y w niepowo\u0142ane r\u0119ce.<\/p>\n<p>Oto pierwsza cz\u0119\u015b\u0107 skryptu <code>deploy.sh<\/code>.<\/p>\n<pre><code class=\"plaintext\">#!\/bin\/sh\nset -e # \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442 \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u043e\u0448\u0438\u0431\u043e\u043a\n\nIMAGE=&quot;&lt;username&gt;\/&lt;repository&gt;&quot; \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # \u041e\u0431\u0440\u0430\u0437 Docker\nGIT_VERSION=$(git describe --always --abbrev --tags --long) # Git-\u0445\u044d\u0448 \u0438 \u0442\u0435\u0433\u0438\n\n# \u0421\u0431\u043e\u0440\u043a\u0430 \u0438 \u0442\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u0430\ndocker build -t ${IMAGE}:${GIT_VERSION} .\ndocker tag ${IMAGE}:${GIT_VERSION} ${IMAGE}:latest\n\n# \u0412\u0445\u043e\u0434 \u0432 Docker Hub \u0438 \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0430 \u043e\u0431\u0440\u0430\u0437\u0430\necho &quot;${DOCKER_PASSWORD}&quot; | docker login -u &quot;${DOCKER_USERNAME}&quot; --password-stdin\ndocker push ${IMAGE}:${GIT_VERSION}<\/code><\/pre>\n<p>\nTo, jak b\u0119dzie wygl\u0105da\u0142a druga cz\u0119\u015b\u0107 skryptu, w pe\u0142ni zale\u017cy od tego, jakiego hosta u\u017cywasz oraz jak zorganizowane jest po\u0142\u0105czenie z nim. W moim przypadku, poniewa\u017c korzystam z Digital Ocean, do po\u0142\u0105czenia z serwerem u\u017cywane s\u0105 polecenia <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/digitalocean\/doctl\">doctl<\/a><\/noindex>. W przypadku pracy z AWS u\u017cywana b\u0119dzie narz\u0119dzie <code>aws<\/code>, i tak dalej.<\/p>\n<p>Konfiguracja serwera nie by\u0142a szczeg\u00f3lnie trudna. Stworzy\u0142am droplet oparty na podstawowym obrazie. Nale\u017cy zauwa\u017cy\u0107, \u017ce wybrany przeze mnie system wymaga jednorazowej r\u0119cznej instalacji Docker oraz jednorazowego r\u0119cznego uruchomienia Docker. Do instalacji Docker u\u017cy\u0142am Ubuntu 18.04, dlatego je\u015bli r\u00f3wnie\u017c u\u017cywasz Ubuntu, aby zrobi\u0107 to samo, mo\u017cesz po prostu post\u0119powa\u0107 zgodnie z <noindex><a rel=\"nofollow\" href=\"https:\/\/phoenixnap.com\/kb\/how-to-install-docker-on-ubuntu-18-04\">tym<\/a><\/noindex> prosty przewodnik.<\/p>\n<p>Nie m\u00f3wi\u0119 tu o konkretnych komendach dla us\u0142ugi, poniewa\u017c ten aspekt mo\u017ce si\u0119 znacznie r\u00f3\u017cni\u0107 w r\u00f3\u017cnych przypadkach. Przedstawi\u0119 jedynie og\u00f3lny plan dzia\u0142ania, kt\u00f3ry wykonuje si\u0119 po po\u0142\u0105czeniu przez SSH z serwerem, na kt\u00f3rym zostanie wdro\u017cony projekt:<\/p>\n<ul>\n<li>Nale\u017cy znale\u017a\u0107 kontener, kt\u00f3ry aktualnie dzia\u0142a, i go zatrzyma\u0107.<\/li>\n<li>Nast\u0119pnie trzeba w tle uruchomi\u0107 nowy kontener.<\/li>\n<li>B\u0119dziesz musia\u0142 ustawi\u0107 lokalny port serwera na warto\u015b\u0107 <code>80<\/code> \u2014 to pozwoli na dost\u0119p do witryny pod adresem w formacie <code>example.com<\/code>, bez podawania portu, a nie korzysta\u0107 z adresu typu <code>example.com:5000<\/code>.<\/li>\n<li>I wreszcie, musisz usun\u0105\u0107 wszystkie stare kontenery i obrazy.<\/li>\n<\/ul>\n<p>\nOto kontynuacja skryptu.<\/p>\n<pre><code class=\"plaintext\"># \u041d\u0430\u0439\u0442\u0438 ID \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\nCONTAINER_ID=$(docker ps | grep takenote | cut -d&quot; &quot; -f1)\n\n# \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0442\u0430\u0440\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439, \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443\ndocker stop ${CONTAINER_ID}\ndocker run --restart unless-stopped -d -p 80:5000 ${IMAGE}:${GIT_VERSION}\ndocker system prune -a -f<\/code><\/pre>\n<p><\/p>\n<h2>Niekt\u00f3re rzeczy, na kt\u00f3re warto zwr\u00f3ci\u0107 uwag\u0119<\/h2>\n<p>\nMo\u017cliwe, \u017ce kiedy po\u0142\u0105czysz si\u0119 z serwerem przez SSH z Travis CI, zobaczysz ostrze\u017cenie, kt\u00f3re uniemo\u017cliwi kontynuowanie instalacji, poniewa\u017c system b\u0119dzie czeka\u0107 na reakcj\u0119 u\u017cytkownika.<\/p>\n<pre><code class=\"plaintext\">Autentyczno\u015b\u0107 hosta '&lt;hostname&gt; (&lt;IP address&gt;)' nie mo\u017ce zosta\u0107 potwierdzona.\nOdcisk palca klucza RSA to &lt;key fingerprint&gt;.\nCzy na pewno chcesz kontynuowa\u0107 po\u0142\u0105czenie (tak\/nie)?<\/code><\/pre>\n<p>\nDowiedzia\u0142am si\u0119, \u017ce klucz tekstowy mo\u017cna zakodowa\u0107 w base64, aby zapisa\u0107 go w formie, w kt\u00f3rej mo\u017cna wygodnie i bezpiecznie pracowa\u0107. Na etapie instalacji mo\u017cna zdekodowa\u0107 klucz publiczny i zapisa\u0107 go w pliku <code>known_hosts<\/code> aby pozby\u0107 si\u0119 opisanego powy\u017cej b\u0142\u0119du.<\/p>\n<pre><code class=\"plaintext\">echo &lt;public key&gt; | base64 # wy\u015bwietla &lt;publiczny klucz zakodowany w base64&gt;<\/code><\/pre>\n<p>\nW praktyce ta komenda mo\u017ce wygl\u0105da\u0107 tak:<\/p>\n<pre><code class=\"plaintext\">echo \"123.45.67.89 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom\/BWDSU\nGPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3\nPbv7kOdJ\/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK\/7XA\nt3FaoJoAsncM1Q9x5+3V0Ww68\/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw\/Pb0rwert\/En\nmZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z\/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx\nNrRFi9wrf+M7Q== you@example.com\" | base64<\/code><\/pre>\n<p>\nA oto jak wygl\u0105da to, co ona wydaje \u2014 ci\u0105g w kodowaniu base64:<\/p>\n<pre><code class=\"plaintext\">123.45.67.89 ssh-rsa AABBA3NzaC1yc2EAAAAB3Nza...QkBzT3UpkDHIzOWFzY1R2M0NOMFY0cWVTcmhrZmo20C...<\/code><\/pre>\n<p>\nOto polecenie, o kt\u00f3rym m\u00f3wiono wcze\u015bniej<\/p>\n<pre><code class=\"plaintext\">install:\n\u00a0\u00a0- echo  | base64 -d &gt;&gt; $HOME\/.ssh\/known_hosts<\/code><\/pre>\n<p>\nTen sam spos\u00f3b mo\u017cna zastosowa\u0107 z kluczem prywatnym przy nawi\u0105zywaniu po\u0142\u0105czenia, poniewa\u017c do uzyskania dost\u0119pu do serwera mo\u017cesz potrzebowa\u0107 klucza prywatnego. Podczas pracy z kluczem musisz jedynie zapewni\u0107 jego bezpieczne przechowywanie w zmiennej \u015brodowiskowej Travis CI oraz nie wypisywa\u0107 go nigdzie.<\/p>\n<p>Kolejn\u0105 rzecz\u0105, na kt\u00f3r\u0105 warto zwr\u00f3ci\u0107 uwag\u0119, jest to, \u017ce by\u0107 mo\u017ce b\u0119dziesz musia\u0142 uruchomi\u0107 ca\u0142y skrypt wdra\u017cania, przedstawiony w postaci jednej linii, na przyk\u0142ad - za pomoc\u0105 <code>doctl<\/code>. Mo\u017ce to wymaga\u0107 dodatkowego wysi\u0142ku.<\/p>\n<pre><code class=\"plaintext\">doctl compute ssh  --ssh-command \"wszystkie polecenia b\u0119d\u0105 tutaj &amp;&amp; tutaj\"<\/code><\/pre>\n<p><\/p>\n<h2>TLS\/SSL i r\u00f3wnowa\u017cenie obci\u0105\u017cenia<\/h2>\n<p>\nPo zrobieniu wszystkiego, o czym m\u00f3wiono wcze\u015bniej, ostatnim problemem, przed kt\u00f3rym stan\u0119\u0142am, by\u0142 brak SSL na serwerze. Poniewa\u017c korzystam z serwera Node.js, aby wymusi\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/www.digitalocean.com\/community\/tutorials\/how-to-secure-a-containerized-node-js-application-with-nginx-let-s-encrypt-and-docker-compose\">dzia\u0142a\u0107<\/a><\/noindex> serwer proxy Nginx i Let\u2019s Encrypt, trzeba sporo nad tym popracowa\u0107.<\/p>\n<p>Nie chcia\u0142am wykonywa\u0107 tych wszystkich ustawie\u0144 SSL r\u0119cznie, wi\u0119c po prostu stworzy\u0142am r\u00f3wnowa\u017cnik obci\u0105\u017cenia i zapisa\u0142am jego dane w DNS. W przypadku DigitalOcean na przyk\u0142ad, stworzenie automatycznie odnawiaj\u0105cego si\u0119 certyfikatu samopodpisanego na r\u00f3wnowa\u017cniku obci\u0105\u017cenia to prosta, bezp\u0142atna i szybka procedura. Taki spos\u00f3b ma r\u00f3wnie\u017c dodatkow\u0105 zalet\u0119, kt\u00f3ra polega na tym, \u017ce pozwala bardzo \u0142atwo skonfigurowa\u0107 SSL na wielu serwerach dzia\u0142aj\u0105cych za r\u00f3wnowa\u017cnikiem obci\u0105\u017cenia. To pozwala serwerom \u201enie my\u015ble\u0107\u201d o SSL, ale jednocze\u015bnie korzysta\u0107 z portu <code>80<\/code>. Tak wi\u0119c konfiguracja SSL na r\u00f3wnowa\u017cniku obci\u0105\u017cenia jest znacznie prostsza i wygodniejsza ni\u017c alternatywne metody konfigurowania SSL.<\/p>\n<p>Teraz mo\u017cna zamkn\u0105\u0107 na serwerze wszystkie porty akceptuj\u0105ce po\u0142\u0105czenia przychodz\u0105ce \u2014 poza portem <code>80<\/code>, u\u017cywanym do komunikacji z r\u00f3wnowa\u017cnikiem obci\u0105\u017cenia, oraz portem <code>22<\/code> dla SSH. W rezultacie pr\u00f3by bezpo\u015bredniego dost\u0119pu do serwera przez jakiekolwiek porty, z wyj\u0105tkiem tych dw\u00f3ch, zako\u0144cz\u0105 si\u0119 niepowodzeniem.<\/p>\n<h2>Podsumowanie<\/h2>\n<p>\nPo tym, jak wykona\u0142am wszystko, o czym m\u00f3wi\u0142am w tym materiale, ju\u017c nie obawia\u0142am si\u0119 ani platformy Docker, ani koncepcji zautomatyzowanych \u0142a\u0144cuch\u00f3w CI\/CD. Uda\u0142o mi si\u0119 skonfigurowa\u0107 \u0142a\u0144cuch ci\u0105g\u0142ej integracji, w ramach kt\u00f3rego testowany jest kod przed jego wdro\u017ceniem do produkcji oraz automatyczne wdro\u017cenie kodu na serwer. Wszystko to jest dla mnie wci\u0105\u017c stosunkowo nowe, i jestem pewna, \u017ce istniej\u0105 sposoby na popraw\u0119 mojego zautomatyzowanego procesu pracy i uczynienie go bardziej efektywnym. Dlatego je\u015bli masz jakie\u015b pomys\u0142y w tej kwestii \u2013 daj <noindex><a rel=\"nofollow\" href=\"mailto:hello@taniarascia.com\">do mnie<\/a><\/noindex> mi zna\u0107. Mam nadziej\u0119, \u017ce ten artyku\u0142 pom\u00f3g\u0142 ci w twoich dzia\u0142aniach. Chcia\u0142abym wierzy\u0107, \u017ce czytaj\u0105c go, dowiedzia\u0142e\u015b si\u0119 tyle, ile ja, podczas gdy analizowa\u0142am wszystko, o czym w nim opisa\u0142am.<\/p>\n<p><b>P.S.<\/b> W naszym <noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/ru-rub\/marketplace#order\">rynkach<\/a><\/noindex> jest dost\u0119pny obraz <noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/ru-rub\/marketplace\/docker#advantages\">Docker<\/a><\/noindex>, kt\u00f3ry mo\u017cna zainstalowa\u0107 jednym klikni\u0119ciem. Mo\u017cesz sprawdzi\u0107 dzia\u0142anie kontener\u00f3w na <noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/\">VPS<\/a><\/noindex>. Wszystkim nowym klientom przys\u0142uguje 3 dni darmowego testowania.<\/p>\n<p><b>Drodzy Czytelnicy!<\/b> Czy korzystasz z technologii CI\/CD w swoich projektach?<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/ru-rub\/#order\"><img decoding=\"async\" alt=\"Tworzenie CI\/CD i automatyzacja pracy z Dockerem\" src=\"\/wp-content\/uploads\/2020\/02\/3fbfa90ea56a3d9b6b89ba945eb94570.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex><br \/>\n<br \/>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/ruvds\/blog\/488668\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e. \u0411\u044b\u043b Apache-\u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430 \u043a\u0430\u043a\u043e\u043c-\u043d\u0438\u0431\u0443\u0434\u044c \u043e\u0431\u0449\u0435\u043c \u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435, \u043d\u0430 \u044d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0432\u043e\u0439\u0442\u0438 \u043f\u043e FTP, \u043d\u0430\u043f\u0438\u0441\u0430\u0432 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 \u043d\u0435\u0447\u0442\u043e \u0432\u0440\u043e\u0434\u0435 ftp:\/\/ftp.example.com. \u041f\u043e\u0442\u043e\u043c \u043d\u0430\u0434\u043e \u0431\u044b\u043b\u043e \u0432\u0432\u0435\u0441\u0442\u0438 \u0438\u043c\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0438 \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440. \u0414\u0440\u0443\u0433\u0438\u0435 \u0431\u044b\u043b\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0430, \u0432\u0441\u0451 \u0442\u043e\u0433\u0434\u0430 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":69352,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-69351","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 CI\/CD-\u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Docker | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-02-18T11:44:06+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-03-03T13:15:00+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Tworzenie \u0142a\u0144cucha CI\/CD i automatyzacja pracy z Dockerem | ProHoster","description":"Moje pierwsze strony internetowe stworzy\u0142am pod koniec lat 90-tych. Wtedy ich uruchomienie by\u0142o niezwykle proste.","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 CI\/CD-\u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Docker | ProHoster","og:description":"\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e.","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-02-18T11:44:06+00:00","article:modified_time":"2020-03-03T13:15:00+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"69351","title":null,"description":null,"keywords":null,"keyphrases":{"focus":[],"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 19:24:45","updated":"2026-08-11 12:50:24","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/69351","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=69351"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/69351\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/69352"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=69351"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=69351"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=69351"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}