Jak zbudować skuteczny początkowy potok wdrożeniowy

Opublikowany: 2022-03-11

Uwielbiam budować różne rzeczy — który programista nie? Uwielbiam wymyślać rozwiązania ciekawych problemów, pisać implementacje i tworzyć piękny kod. Jednak to, co mi się nie podoba, to operacje . Operacje to wszystko, co nie jest związane z tworzeniem doskonałego oprogramowania — wszystko, od konfiguracji serwerów po dostarczenie kodu do produkcji.

Jest to interesujące, ponieważ jako niezależny programista Ruby on Rails często muszę tworzyć nowe aplikacje internetowe i powtarzać proces odkrywania strony DevOps. Na szczęście po stworzeniu dziesiątek aplikacji w końcu zdecydowałem się na doskonały wstępny potok wdrożeniowy. Niestety, nie wszyscy zrozumieli to tak jak ja — w końcu ta wiedza doprowadziła mnie do podjęcia decyzji i udokumentowania mojego procesu.

W tym artykule przeprowadzę Cię przez mój idealny potok do wykorzystania na początku projektu. W moim potoku każde wypchnięcie jest testowane, gałąź główna jest wdrażana do przemieszczania ze świeżym zrzutem bazy danych z produkcji, a wersjonowane tagi są wdrażane do produkcji, a kopie zapasowe i migracje odbywają się automatycznie.

Uwaga, ponieważ jest to mój potok, jest również uparty i dostosowany do moich potrzeb; jednak możesz swobodnie wymienić wszystko, co ci się nie podoba, i zastąpić to, co ci się podoba. Dla mojego potoku użyjemy:

  • GitLab do kodu hosta.
    • Dlaczego: Moi klienci wolą, aby ich kod pozostawał tajny, a bezpłatna warstwa GitLab jest wspaniała. Ponadto zintegrowane bezpłatne CI jest niesamowite. Dzięki GitLab!
    • Alternatywy: GitHub, BitBucket, AWS CodeCommit i wiele innych.
  • GitLab CI do budowania, testowania i wdrażania naszego kodu.
    • Dlaczego: integruje się z GitLab i jest bezpłatny!
    • Alternatywy: TravisCI, Codeship, CircleCI, DIY with Fabric8 i wiele innych.
  • Heroku do hostowania naszej aplikacji.
    • Dlaczego: Działa po wyjęciu z pudełka i jest idealną platformą do rozpoczęcia. Możesz to zmienić w przyszłości, ale nie każda nowa aplikacja musi działać w specjalnie zbudowanym klastrze Kubernetes. Nawet Coinbase zaczął od Heroku.
    • Alternatywy: AWS, DigitalOcean, Vultr, DIY z Kubernetes i wiele innych.

Old-school: Utwórz podstawową aplikację i wdróż ją w Heroku

Najpierw odtwórzmy typową aplikację dla kogoś, kto nie używa żadnych wymyślnych potoków CI/CD i po prostu chce wdrożyć swoją aplikację.

Schemat tradycyjnego hostingu kodu i działań wdrożeniowych

Nie ma znaczenia, jaki rodzaj aplikacji tworzysz, ale będziesz potrzebować Yarn lub npm. Na przykład tworzę aplikację Ruby on Rails, ponieważ zawiera ona migracje i CLI, i mam już napisaną dla niej konfigurację. Możesz używać dowolnego frameworka lub języka, który wolisz, ale będziesz potrzebować Yarn do wersjonowania, które robię później. Tworzę prostą aplikację CRUD przy użyciu tylko kilku poleceń i bez uwierzytelniania.

Sprawdźmy, czy nasza aplikacja działa zgodnie z oczekiwaniami. Poszedłem dalej i stworzyłem kilka postów, żeby się upewnić.

Aplikacja działająca w fazie rozwoju

Wdróżmy go w Heroku, wypychając nasz kod i uruchamiając migracje

 $ heroku create toptal-pipeline Creating ⬢ toptal-pipeline... done https://toptal-pipeline.herokuapp.com/ | https://git.heroku.com/toptal-pipeline.git $ git push heroku master Counting objects: 132, done. ... To https://git.heroku.com/toptal-pipeline.git * [new branch] master -> master $ heroku run rails db:migrate Running rails db:migrate on ⬢ toptal-pipeline... up, run.9653 (Free) ...

Na koniec przetestujmy to w produkcji

Aplikacja działająca w środowisku produkcyjnym

I to wszystko! Zazwyczaj jest to miejsce, w którym większość programistów opuszcza swoją działalność. W przyszłości, jeśli wprowadzisz zmiany, będziesz musiał powtórzyć powyższe kroki wdrażania i migracji. Możesz nawet przeprowadzić testy, jeśli nie spóźnisz się na obiad. Jest to świetny punkt wyjścia, ale zastanówmy się nad tą metodą nieco więcej.

Plusy

  • Szybka konfiguracja.
  • Wdrożenia są łatwe.

Cons

  • Not DRY: wymaga powtórzenia tych samych kroków przy każdej zmianie.
  • Brak wersji: „Wycofuję wczorajsze wdrożenie do zeszłotygodniowego” nie jest bardzo konkretne za trzy tygodnie od teraz.
  • Niezły kod-odporny: wiesz, że powinieneś przeprowadzać testy, ale nikt nie patrzy, więc możesz go popchnąć pomimo okazjonalnego zepsutego testu.
  • Nieźle odporny na aktorów: Co się stanie, jeśli niezadowolony programista zdecyduje się zepsuć Twoją aplikację, wciskając kod z wiadomością o tym, że nie zamawiasz wystarczającej ilości pizzy dla swojego zespołu?
  • Nie skaluje się: umożliwienie każdemu programiście wdrożenia umożliwiłoby im dostęp do aplikacji na poziomie produkcyjnym, naruszając zasadę najmniejszych uprawnień.
  • Brak środowiska przejściowego: Błędy specyficzne dla środowiska produkcyjnego nie pojawią się do czasu produkcji.

Idealny wstępny potok wdrożeniowy

Dzisiaj spróbuję czegoś innego: porozmawiajmy hipotetycznie. Przekażę „Wam” głos i porozmawiamy o tym, jak możemy poprawić ten przepływ prądu. Śmiało, powiedz coś.

Co powiedzieć? Czekaj – mogę porozmawiać?

Tak, właśnie to miałem na myśli, dając ci głos. Jak się masz?

Jestem dobry. To dziwne uczucie

Rozumiem, ale po prostu rzuć się z tym. Porozmawiajmy teraz o naszym rurociągu. Jaka jest najbardziej irytująca część uruchamiania wdrożeń?

Och, to proste. Ilość czasu, który marnuję. Czy kiedykolwiek próbowałeś przepychać się do Heroku?

Tak, oglądanie pobierania zależności i tworzenia aplikacji w ramach git push jest okropne!

Wiem, prawda? To niesamowite. Chciałbym tego nie robić. Jest też fakt, że muszę przeprowadzić migracje *po* wdrożeniu, więc muszę obejrzeć pokaz i sprawdzić, czy moje wdrożenie przebiega

Ok, możesz rozwiązać ten ostatni problem, łącząc dwa polecenia za pomocą && , na przykład git push heroku master && heroku run rails db:migrate , lub po prostu tworząc skrypt bash i umieszczając go w swoim kodzie, ale nadal świetna odpowiedź, czas i powtarzanie to prawdziwy ból.

Tak, to naprawdę jest do bani

Co jeśli powiem ci, że możesz natychmiast naprawić ten bit za pomocą potoku CI/CD?

Co teraz? Co to jest?

CI/CD oznacza ciągłą integrację (CI) i ciągłe dostarczanie/wdrażanie (CD). Trudno było mi dokładnie zrozumieć, co to było, kiedy zaczynałem, ponieważ wszyscy używali niejasnych terminów, takich jak „połączenie rozwoju i operacji”, ale mówiąc po prostu:

  • Ciągła integracja: upewnienie się, że cały kod jest scalony w jednym miejscu. Zaproś swój zespół do korzystania z Git, a będziesz korzystać z CI.
  • Ciągłe dostarczanie: upewnienie się, że Twój kod jest stale gotowy do wysyłki. Oznacza to, że szybko wyprodukujesz wersję swojego produktu do dystrybucji.
  • Ciągłe wdrażanie: Bezproblemowe przenoszenie produktu z ciągłego dostarczania i wdrażanie go na serwerach.

Och, teraz rozumiem. Chodzi o to, aby moja aplikacja magicznie wdrożyła się w świat!

Mój ulubiony artykuł wyjaśniający CI/CD jest autorstwa Atlassian tutaj. To powinno wyjaśnić wszystkie Twoje pytania. W każdym razie wracając do problemu.

Tak, wracając do tego. Jak uniknąć ręcznego wdrażania?

Konfigurowanie potoku CI/CD do wdrożenia w trybie Push to master

A gdybym ci powiedział, że możesz natychmiast naprawić ten bit za pomocą CI/CD? Możesz wepchnąć do swojego pilota GitLab ( origin ), a komputer zostanie spawnowany, aby po prostu wepchnąć ten twój kod do Heroku.

Nie ma mowy!

Tak, sposób! Wróćmy z powrotem do kodu.

Schemat prostego potoku wdrażania CI/CD

Utwórz plik .gitlab-ci.yml z następującą zawartością, zamieniając toptal-pipeline na nazwę aplikacji Heroku:

 image: ruby:2.4 before_script: - > : "${HEROKU_EMAIL:?Please set HEROKU_EMAIL in your CI/CD config vars}" - > : "${HEROKU_AUTH_TOKEN:?Please set HEROKU_AUTH_TOKEN in your CI/CD config vars}" - curl https://cli-assets.heroku.com/install-standalone.sh | sh - | cat >~/.netrc <<EOF machine api.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN machine git.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN EOF - chmod 600 ~/.netrc - git config --global user.email "[email protected]" - git config --global user.name "CI/CD" variables: APPNAME_PRODUCTION: toptal-pipeline deploy_to_production: stage: deploy environment: name: production url: https://$APPNAME_PRODUCTION.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_PRODUCTION.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku run rails db:migrate --app $APPNAME_PRODUCTION only: - master

Podnieś to i obserwuj, jak kończy się niepowodzeniem na stronie Potoki projektu. To dlatego, że brakuje kluczy uwierzytelniających dla twojego konta Heroku. Naprawienie tego jest jednak dość proste. Najpierw będziesz potrzebować klucza Heroku API . Pobierz go ze strony Zarządzaj kontem, a następnie dodaj następujące tajne zmienne w ustawieniach CI/CD repozytorium GitLab:

  • HEROKU_EMAIL : Adres e-mail, którego używasz do logowania się do Heroku
  • HEROKU_AUTH_KEY : klucz, który dostałeś od Heroku

Obraz tajnych zmiennych na stronie ustawień GitLab CI/CD

Powinno to spowodować wdrożenie działającego GitLab do Heroku przy każdym naciśnięciu. Co się dzieje:

  • Po przejściu do mistrzostwa
    • Heroku CLI jest instalowane i uwierzytelniane w kontenerze.
    • Twój kod zostanie przekazany do Heroku.
    • Kopia zapasowa Twojej bazy danych jest zapisywana w Heroku.
    • Migracje są prowadzone.

Już teraz widać, że nie tylko oszczędzasz czas, automatyzując wszystko do git push , ale także tworzysz kopię zapasową swojej bazy danych przy każdym wdrożeniu! Jeśli coś pójdzie nie tak, będziesz mieć kopię swojej bazy danych, do której możesz wrócić.

Tworzenie środowiska scenicznego

Ale czekaj, szybkie pytanie, co dzieje się z problemami związanymi z produkcją? Co się stanie, jeśli natkniesz się na dziwny błąd, ponieważ twoje środowisko programistyczne jest zbyt różne od produkcyjnego? Kiedyś natknąłem się na dziwne problemy z SQLite 3 i PostgreSQL, gdy przeprowadzałem migrację. Konkrety mi umykają, ale to całkiem możliwe.

Ściśle używam PostgreSQL w rozwoju, nigdy nie błędnie dopasowuję silników baz danych i pilnie monitoruję swój stos pod kątem potencjalnych niezgodności.

Cóż, to żmudna praca i pochwalam twoją dyscyplinę. Osobiście jestem zbyt leniwy, żeby to zrobić. Czy możesz jednak zagwarantować ten poziom staranności wszystkim potencjalnym przyszłym programistom, współpracownikom lub współpracownikom?

Errrr… Tak, nie. Tu mnie masz. Inni ludzie to zepsują. Ale o co ci chodzi?

Chodzi mi o to, że potrzebujesz środowiska inscenizacyjnego. To jak produkcja, ale tak nie jest. Środowisko pomostowe to miejsce, w którym przeprowadzasz próby wdrożenia do środowiska produkcyjnego i wcześnie wyłapujesz wszystkie błędy. Moje środowiska pomostowe zwykle odzwierciedlają produkcję i zrzucam kopię produkcyjnej bazy danych podczas wdrażania pomostowego, aby upewnić się, że żadne nieprzyjemne przypadki nie zepsują moich migracji. Dzięki środowisku inscenizacyjnemu możesz przestać traktować swoich użytkowników jak świnki morskie.

To ma sens! Więc jak mam to zrobić?

Oto, gdzie robi się ciekawie. Lubię wdrażać master bezpośrednio do inscenizacji.

Czekaj, czy to nie tam właśnie wdrażamy produkcję?

Tak, ale teraz będziemy wdrażać zamiast tego.

Ale jeśli master zostanie wdrożony do etapu tymczasowego, jak wdrożymy go do produkcji?

Używając czegoś, co powinieneś robić lata temu: Wersjonowanie naszego kodu i umieszczanie tagów Git.

Tagi Gita? Kto używa tagów Git?! To zaczyna brzmieć jak dużo pracy.

Na pewno było, ale na szczęście wykonałem już całą tę pracę i możesz po prostu zrzucić mój kod i zadziała.

Omówienie sposobu działania wdrożeń przejściowych i produkcyjnych

Najpierw dodaj blok dotyczący wdrażania tymczasowego do pliku .gitlab-ci.yml , stworzyłem nową aplikację Heroku o nazwie toptal-pipeline-staging :

 … variables: APPNAME_PRODUCTION: toptal-pipeline APPNAME_STAGING: toptal-pipeline-staging deploy_to_staging: stage: deploy environment: name: staging url: https://$APPNAME_STAGING.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_STAGING.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku pg:backups:restore `heroku pg:backups:url --app $APPNAME_PRODUCTION` --app $APPNAME_STAGING --confirm $APPNAME_STAGING - heroku run rails db:migrate --app $APPNAME_STAGING only: - master - tags ...

Następnie zmień ostatnią linię swojego bloku produkcyjnego na semantycznie wersjonowane tagi Git zamiast gałęzi master:

 deploy_to_production: ... only: - /^v(?'MAJOR'(?:0|(?:[1-9]\d*)))\.(?'MINOR'(?:0|(?:[1-9]\d*)))\.(?'PATCH'(?:0|(?:[1-9]\d*)))(?:-(?'prerelease'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(?:\+(?'build'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$/ # semver pattern above is adapted from https://github.com/semver/semver.org/issues/59#issuecomment-57884619

Uruchomienie tego teraz zakończy się niepowodzeniem, ponieważ GitLab jest na tyle sprytny, że zezwala tylko „chronionym” gałęziom na dostęp do naszych tajnych zmiennych. Aby dodać tagi wersji, przejdź do strony ustawień repozytorium projektu GitLab i dodaj v* do chronionych tagów.

Obraz tagu wersji dodawanego do chronionych tagów na stronie ustawień repozytorium

Podsumujmy, co się teraz dzieje:

  • Po wypchnięciu do wzorca lub wypchnięciu oznaczonego zatwierdzenia
    • Heroku CLI jest instalowane i uwierzytelniane w kontenerze.
    • Twój kod zostanie przekazany do Heroku.
    • Kopia zapasowa Twojej produkcji bazy danych jest zapisywana w Heroku.
    • Kopia zapasowa jest zrzucana w środowisku przejściowym.
    • Migracje są uruchamiane na tymczasowej bazie danych.
  • Po wypchnięciu zatwierdzenia oznaczonego semantycznie wersji
    • Heroku CLI jest instalowane i uwierzytelniane w kontenerze.
    • Twój kod zostanie przekazany do Heroku.
    • Kopia zapasowa Twojej produkcji bazy danych jest zapisywana w Heroku.
    • Migracje są uruchamiane na produkcyjnej bazie danych.

Czy czujesz się teraz potężny? Czuję się potężny. Pamiętam, kiedy pierwszy raz zaszedłem tak daleko, zadzwoniłem do żony i wyjaśniłem cały ten rurociąg z straszliwymi szczegółami. A ona nie jest nawet techniczna. Byłem pod wrażeniem siebie, a ty też powinieneś! Świetna robota już tak daleko!

Testowanie każdego pchnięcia

Ale to nie wszystko, ponieważ komputer i tak robi za ciebie różne rzeczy, może również uruchamiać wszystkie rzeczy, na które jesteś zbyt leniwy: testy, błędy lintingu, prawie wszystko, co chcesz zrobić, a jeśli którekolwiek z nich zawiedzie, wygrają nie przejść do wdrożenia.

Uwielbiam mieć to w planach, dzięki czemu moje recenzje kodu są zabawne. Jeśli prośba o scalenie przejdzie przez wszystkie moje kontrole kodu, zasługuje na sprawdzenie.

Obraz testowania przy każdym naciśnięciu

Dodaj blok test :

 test: stage: test variables: POSTGRES_USER: test POSTGRES_PASSSWORD: test-password POSTGRES_DB: test DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSSWORD}@postgres/${POSTGRES_DB} RAILS_ENV: test services: - postgres:alpine before_script: - curl -sL https://deb.nodesource.com/setup_8.x | bash - apt-get update -qq && apt-get install -yqq nodejs libpq-dev - curl -o- -L https://yarnpkg.com/install.sh | bash - source ~/.bashrc - yarn - gem install bundler --no-ri --no-rdoc - bundle install -j $(nproc) --path vendor - bundle exec rake db:setup RAILS_ENV=test script: - bundle exec rake spec - bundle exec rubocop

Podsumujmy, co się teraz dzieje:

  • Przy każdym wypchnięciu lub prośbie o scalenie
    • Ruby i Node są ustawione w kontenerze.
    • Zależności są instalowane.
    • Aplikacja jest testowana.
  • Po wypchnięciu do mastera lub wypchnięciu otagowanego zatwierdzenia i tylko wtedy, gdy wszystkie testy zakończą się pomyślnie
    • Heroku CLI jest instalowane i uwierzytelniane w kontenerze.
    • Twój kod zostanie przekazany do Heroku.
    • Kopia zapasowa Twojej produkcji bazy danych jest zapisywana w Heroku.
    • Kopia zapasowa jest zrzucana w środowisku przejściowym.
    • Migracje są uruchamiane na tymczasowej bazie danych.
  • Po wypchnięciu zatwierdzenia oznaczonego semantycznie wersji i tylko wtedy, gdy wszystkie testy zakończą się pomyślnie
    • Heroku CLI jest instalowane i uwierzytelniane w kontenerze.
    • Twój kod zostanie przekazany do Heroku.
    • Kopia zapasowa Twojej produkcji bazy danych jest zapisywana w Heroku.
    • Migracje są uruchamiane na produkcyjnej bazie danych.

Zrób krok wstecz i podziwiaj osiągnięty poziom automatyzacji. Od teraz wszystko, co musisz zrobić, to napisać kod i wcisnąć. Przetestuj swoją aplikację ręcznie w fazie inscenizacji, jeśli masz na to ochotę, a kiedy poczujesz się wystarczająco pewnie, aby wypchnąć ją w świat, oznacz ją wersjonowaniem semantycznym!

Automatyczne wersjonowanie semantyczne

Tak, jest idealnie, ale czegoś brakuje. Nie lubię sprawdzać ostatniej wersji aplikacji i wyraźnie ją oznaczać. To wymaga wielu poleceń i rozprasza mnie na kilka sekund.

Dobra koleś, przestań! Wystarczy. Po prostu przesadzasz z inżynierią. To działa, jest genialne, nie psuj dobrego, przesadzając.

Dobra, mam dobry powód, żeby robić to, co mam zamiar zrobić.

Módl się, oświeć mnie.

Kiedyś byłem taki jak ty. Byłem zadowolony z tej konfiguracji, ale potem nawaliłem. git tag wyświetla tagi w kolejności alfabetycznej, v0.0.11 jest powyżej v0.0.2 . Kiedyś przypadkowo oznaczyłem wydanie i kontynuowałem to przez około pół tuzina wydań, aż zobaczyłem swój błąd. Wtedy też postanowiłem to zautomatyzować.

Znowu się zaczyna

Ok, więc na szczęście mamy do dyspozycji moc npm, więc znalazłem odpowiedni pakiet: Run yarn add --dev standard-version i dodaj następujące elementy do swojego pliku package.json :

 "scripts": { "release": "standard-version", "major": "yarn release --release-as major", "minor": "yarn release --release-as minor", "patch": "yarn release --release-as patch" },

Teraz musisz zrobić ostatnią rzecz, skonfigurować Git tak, aby domyślnie wysyłał tagi. W tej chwili musisz uruchomić git push --tags , aby wypchnąć tag w górę, ale automatyczne wykonanie tego przy zwykłym git push jest tak proste, jak uruchomienie git config --global push.followTags true .

Aby użyć nowego potoku, gdy chcesz utworzyć przebieg wydania:

  • yarn patch do uwalniania łatek
  • yarn minor dla mniejszych wydań
  • yarn major dla głównych wydań

Jeśli nie masz pewności, co oznaczają słowa „major”, „minor” i „patch”, przeczytaj więcej na ten temat w witrynie wersjonowania semantycznego.

Teraz, gdy już ukończyłeś swój potok, przypomnijmy, jak z niego korzystać!

  • Napisz kod.
  • Zatwierdź i wypchnij go, aby przetestować i wdrożyć go do przemieszczania.
  • Użyj yarn patch aby oznaczyć uwolniony plaster.
  • git push , aby wypchnąć go do produkcji.

Podsumowanie i dalsze kroki

Dopiero co zarysowałem powierzchnię tego, co jest możliwe z potokami CI/CD. To dość uproszczony przykład. Możesz zrobić o wiele więcej, zamieniając Heroku na Kubernetes. Jeśli zdecydujesz się użyć GitLab CI, przeczytaj dokumentację yaml, ponieważ możesz zrobić o wiele więcej, buforując pliki między wdrożeniami lub zapisując artefakty!

Kolejną ogromną zmianą, którą można wprowadzić w tym potoku, jest wprowadzenie zewnętrznych wyzwalaczy do uruchamiania wersji semantycznej i zwalniania. Obecnie ChatOps jest częścią ich płatnego planu i mam nadzieję, że udostępnią go w bezpłatnych planach. Ale wyobraź sobie, że możesz uruchomić następny obraz za pomocą jednego polecenia Slack!

Schemat potoku wdrażania CI/CD, w którym wdrożenia produkcyjne są uruchamiane zewnętrznie, prawdopodobnie za pośrednictwem czatu lub webhooków

W końcu, gdy aplikacja zaczyna się rozrastać i wymaga zależności na poziomie systemu, może być konieczne użycie kontenera. Gdy tak się stanie, zapoznaj się z naszym poradnikiem: Pierwsze kroki z Dockerem: Simplifying Devops .

Ta przykładowa aplikacja naprawdę działa, a jej kod źródłowy można znaleźć tutaj.