
Zgodnie z obietnicą z poprzedniego wpisu tej serii, dziś przedstawię drugą część na temat migracji mojego bloga z Wordpressa do Jekylla. Jej tematem będzie deployment do Heroku czyli hostowanie bloga w chmurze. Ogólnie nie jest to nic specjalnie skomplikowanego - zresztą sam się zaraz przekonasz. Zapraszam do lektury!

Migracja bloga #2: Deployment do Heroku
Zgodnie z obietnicą z poprzedniego wpisu tej serii, dziś przedstawię drugą część na temat migracji mojego bloga z Wordpressa do Jekylla. Jej tematem będzie deployment do Heroku czyli hostowanie bloga w chmurze. Ogólnie nie jest to nic specjalnie skomplikowanego - zresztą sam się zaraz przekonasz. Zapraszam do lektury!
Co to jest Heroku?
Zgodnie z tym co napisano na stronie “About” serwisu Heroku, jest to oparta o kontenery platforma do hostowania aplikacji w chmurze (ang. Platform as a Service czyli PaaS). Usługa ta umożliwia łatwy deployment, zarządzanie oraz skalowanie nowoczesnych aplikacji internetowych. Dzięki temu twórcy aplikacji mogą skupić się na tworzeniu swoich produktów zamiast martwić się o infrastrukturę, serwery i inne tego typu “pierdoły”.
W dzisiejszym wpisie skupię się tylko na deploymencie, który w Heroku oparty jest o repozytorium Git. Uzyskujemy do niego dostęp po założeniu konta w Heroku i utworzeniu aplikacji (można to zrobić w serwisie lub z konsoli). Następnie wystarczy zainstalować Heroku CLI i wywołać polecenie w konsoli:
heroku login
Po wprowadzeniu loginu i hasła jesteśmy zalogowani w Heroku. Repozytorium Git w Heroku traktuje się raczej jako sposób na deployment niż jako główne miejsce do przechowywania plików projektu. Te zwykle trzyma się w osobnym, zewnętrznym repozytorium, na przykład w GitHubie lub BitBucket. W takim przypadku, mając już utworzone repozytorium Git, wystarczy dodać do niego dodatkowe remote
, które wskaże na repo Heroku:
heroku git:remote -a nafrontendzie-blog
Od tej pory, możemy “pushować” nasze zmiany do Heroku:
git push heroku master
Po każdym “pushu” Heroku może dla nas wykonać określone operacje (na przykład dla aplikacji Node.js może to być jakaś komenda npm scripts
itp.). Za chwilę wykorzystamy tę możliwość przy deploymencie bloga.
Blog oparty na Jekyllu - deployment do Heroku
Po tym krótkim wprowadzeniu czas teraz przejść do części właściwej czyli konfiguracji deploymentu bloga opartego na Jekyllu. Cały proces rozpoczniemy od instalacji odpowiednich pakietów Ruby. Robimy to poprzez dodanie odpowiednich wpisów do pliku Gemfile
. Plik ten wygląda u mnie tak (pokazywałem go już ostatnio):
source "https://rubygems.org"
ruby '2.4.1'
gem "jekyll", "3.6.0.pre.beta1"
gem 'json'
# plugins
gem 'jekyll-sitemap'
gem 'jekyll-twitter-plugin'
# for deployment
gem 'rack-contrib'
gem 'rack-rewrite'
gem 'rake'
gem 'puma'
group :jekyll_plugins do
gem "jekyll-feed", "~> 0.6"
end
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Wymagane pakiety
Najważniejsze, na co musisz zwrócić uwagę, to cztery linie oznaczone komentarzem # for deployment
. Są to pakiety, które będą nam potrzebne do deploymentu.
Pierwszy z nich - rack-contrib
(link) to zestaw rozszerzeń dla intefejsu web-servera Rack dostępnego w Ruby. Wykorzystamy go m.in. do rozwiązywania adresów URL, tak aby nie było potrzeby wyświetlania rozszerzenia *.html
dla podstron (i do kilku innych rzeczy).
Pakiet rack-rewrite
(link) pozwoli skonfigurować przekierowania 301 - struktura bloga została niech zmieniona podczas migracji i zaistniała potrzeba odpowiedniego przekierowania niektórych zasobów na nowe adresy.
Gem rake
to program podobny do znanego chyba każdemu programu make
- pozwala on na uruchamianie skryptów pisanych za pomocą składni Ruby. Skrypt taki pozwoli nam na budowanie Jekylla po stronie Heroku, dzięki czemu nie będzie potrzeby “commitowania” katalogu _site
do repozytorium.
Na koniec pakiet puma. Jest to lekki web-server kompatybilny z Rack. Będzie on odpalany przez Heroku i w ten sposób będą serwowane statyczne pliki bloga.
Mając w powyższy sposób zdefiniowany plik Gemfile
wystarczy uruchomić bundler
aby zainstalować wszystkie pakiety i zaktualizować plik Gemfile.lock
:
bundle
Definicja platform buildowania
Heroku jest takie “mądre”, że umie rozpoznać język w jakim napisana jest aplikacja. W naszym przypadku rozpozna więc, że jest to Ruby. Po rozpoznaniu języka projektu, Heroku będzie próbować wykonywać niezbędne instalacje pakietów dla danej platformy po każdym “pushu” - w przypadku Ruby będzie to więc uruchomienie “bundlera” i zainstalowanie na serwerze pakietów wymienionych w pliku Gemfile
!
Jest to super opcja, ja jednak potrzebowałem zrobić coś jeszcze… Otóż skrypty JavaScript i CSS dla mojego bloga ogarniane są za pomocą webpacka. Potrzebowałem więc, aby po “pushu”, Heroku uruchomiło polecenie npm install
oraz wykonało produkcyjny “build” webpacka. Aby to zrobić, musiałem poinformować Heroku, że mój blog to aplikacja nie tylko Ruby ale też Node.js!
W Heroku, za deployment dla poszczególnych platform odpowiadają tzw. “buildpacki”. Możemy sprawdzić jakie “buildpacki” są zdefiniowane dla nasze aplikacji wywołując poniższą komendę:
heroku buildpacks
Oczekiwany przeze mnie efekt tej komendy to:
=== nafrontendzie-blog Buildpack URLs
1. heroku/nodejs
2. heroku/ruby
Jeśli jest inny to najlepiej najpierw wyczyścić wszystkie “buildpacki”:
heroku buildpacks:clear
Następnie możemy dodać obsługę Node.js:
heroku buildpacks:add --index 1 heroku/nodejs
Zwróć uwagę na parametr --index 1
- spowoduje on, że operacje dla Node.js będą wykonywane jako pierwsze. Dodajmy teraz jeszcze obsługę Ruby:
heroku buildpacks:add --index 2 heroku/ruby
Powyższe polecenie doda obsługę Ruby na drugim miejscu.
Od teraz, każdy “push” do repozytorium Heroku, spowoduje najpierw uruchomienie kroków deploymentu Node.js (czyli de facto npm install
/yarn
), a dopiero po nich uruchomione zostaną kroki niezbędne do zbudowania aplikacji Ruby (tutaj do gry wchodzi polecenie bundle
i plik Gemfile
). Oczywiście można konfigurować co się dokładnie dzieje ale to temat na osobny wpis - w moim przypadku nie trzeba było nic dodatkowo robić.
Build webpacka na Heroku
Mając zdefiniowaną odpowiednią kolejność “buildpacków” mamy pewność, że Heroku w pierwszej kolejności zainstaluje wszystkie pakiety znajdujące się w pliku package.json
. Kiedy to się stanie, chciałbym aby wykonany został produkcyjny “build” webpacka. Aby to zrobić, wystarczy do sekcji scripts
pliku package.json
dodać skrypt o nazwie heroku-postbuild
- jeśli Heroku napotka ten skrypt, wykona go zaraz po instalacji pakietów. Spójrz jakie skrypty zdefiniowałem u siebie:
"scripts": {
"start:js": "NODE_ENV=dev webpack",
"start:www": "jekyll serve --watch --future",
"build:js": "NODE_ENV=prod webpack",
"build:www": "JEKYLL_ENV=production jekyll build",
"critical": "node ./tools/critical-css.js",
"crop": "node ./tools/crop-images.js",
"deploy": "yarn build:js && yarn build:www && yarn critical || true && git add . || true && git commit -m \"deployment\" || true && git push || true && git push heroku",
"heroku-postbuild": "yarn build:js"
}
Jak widzisz, na końcu zdefiniowałem skrypt heroku-postbuild
, który uruchamia skrypt build:js
, a ten z kolei buduje produkcyjną wersję webpackowych “bundli”. Dzięki temu to wszystko zadzieje się na serwerach Heroku!
Build Jekylla na Heroku
Podobny efekt jak w przypadku webpacka chciałem uzyskać dla Jekylla. Idealnie by było gdyby polecenie…
JEKYLL_ENV=production jekyll build
…dało się wykonać automatycznie na serwerze.
Aby to osiągnąć wykorzystamy dodany wcześniej do pliku Gemfile
pakiet rake
i zdefiniujemy skrypt, który wywoła odpowiednią komendę po instalacji niezbędnych pakietów Ruby. Do tego celu, w głównym katalogu projektu bloga utworzyłem plik Rakefile
z taką oto zawartością:
namespace :assets do
task :precompile do
puts `JEKYLL_ENV=production bundle exec jekyll build`
end
end
Sama obecność pliku Rakefile
spowoduje, że Heroku odczyta go automatycznie i wykona zawarte w nim operacje. Powyższy skrypt dodaje polecenie budowania Jekylla do zadania rake assets:precompile
wykonywanego przez Heroku.
W tym momencie możemy dodać katalog _site
do pliku .gitignore
!
Tutaj jeszcze mała uwaga: Heroku, podczas procesu deploymentu dodaje katalog
vendor
do głównego katalogu projektu. Trzeba go dodać do atrybutuexclude
w pliku_config.yml
aby wszystko działało!
Konfiguracja web-serwera
Na koniec pozostaje nam tylko konfiguracja web-serwera, który będzie serwować pliki bloga odwiedzającym. Do tego celu wykorzystamy wspomniany wcześniej interfejs webservera rack
, zestaw rozszerzeń rack-contrib
oraz bibliotekę rack-rewrite
. Cała niezbędna konfiguracja znajduje się w pliku config.ru
w głównym katalogu projektu i u mnie wygląda następująco:
require 'rack'
require 'rack/contrib/try_static'
gem 'rack-rewrite', '~> 1.5.0'
require 'rack/rewrite'
use Rack::Rewrite do
r301 %r{.*}, 'https://www.nafrontendzie.pl$&', :scheme => 'http'
r301 %r{.*}, 'https://www.nafrontendzie.pl$&', :if => Proc.new {|rack_env|
rack_env['SERVER_NAME'] != 'www.nafrontendzie.pl' and rack_env['rack.url_scheme'] == 'https'
}
r301 %r{^/category/programowanie/(.*)$}, '/kategoria/programowanie'
r301 %r{^/category/przemyslenia/(.*)$}, '/kategoria/opinie'
r301 %r{^/(.*)/$}, '/$1'
r301 %r{^/(.*)/(\?.*)$}, '/$1$2'
r301 '/category/programowanie', '/kategoria/programowanie'
r301 '/category/przemyslenia', '/kategoria/opinie'
end
# enable compression
use Rack::Deflater
# static configuration (file path matches reuest path)
use Rack::TryStatic,
:root => "_site", # static files root dir
:urls => %w[/], # match all requests
:try => ['.html', 'index.html', '/index.html'], # try these postfixes sequentially
:gzip => true, # enable compressed files
:header_rules => [
[['css', 'js', 'jpg', 'png', 'jpeg', 'gif'], {'Cache-Control' => 'public, max-age=604800'}]
]
# otherwise 404 NotFound
notFoundPage = File.open('_site/404.html').read
run lambda { |_| [404, {'Content-Type' => 'text/html'}, [notFoundPage]]}
Z interesujących rzeczy w powyższym kodzie: w sekcji Rack::Rewrite
mamy definicję przekierowań 301; dalej, w sekcji Rack::TryStatic
definiujemy translację adresów w stylu /jakis-adres
na adres /jakis-adres.html
; na koniec dodajemy jeszcze obsługę strony 404.
Pozostała nam już ostatnia rzecz do wykonania: poinformowanie Heroku, co ma zrobić po zainstalowaniu wszystkich pakietów i uruchomieniu wszystkich zdefiniowanych powyżej komend. Do tego celu służy plik Procfile
, który u mnie wygląda następująco:
web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-production}
console: echo console
rake: echo rake
Powyższe spowoduje uruchomienie web-servera puma
na Heroku.
Skrypt deploymentu
Powyżej pokazałem sekcję scripts
pliku package.json
. Znajduje się tam skrypt o nazwie deploy
, który wygląda tak:
"deploy": "yarn build:js && yarn build:www && yarn critical || true && git add . || true && git commit -m \"deployment\" || true && git push || true && git push heroku"
Wywołuje on kilka innych skryptów npm, takich jak build:js
, build:www
czy critical
. Dwa pierwsze budują webpacka i Jekylla lokalnie - robię to głównie w celu upewnienia się, że wszystko jest OK. Trzeci z nich uruchamia generowanie krytycznego kodu CSS, który potem wklejam “inline” do sekcji head
pliku HTML (w celu optymalizacji szybkości wczytywania strony) - to temat na osobny wpis.
Następnie, w powyższym skrypcie odpalam komendy “commitujące” i “pushujące” ewentualne zmiany do Gita (GitHub). Najważniejsze jest ostanie polecenie: git push heroku
- powoduje ono “pushnięcie” zmian do repozytorium Heroku, co będzie skutkowało uruchomieniem na serwerach Heroku, po kolei:
- instalacji wymaganych pakietów Node.js
- produkcyjnego builda webpacka
- instalacji pakietów Ruby
- produkcyjnego builda Jekylla
- odpaleniem web-servera
puma
I to by było na tyle - cały deployment chwilę trwa ale wszystko dzieje się automatycznie, a cały proces uruchamiany jest jedną komendą.
Podsumowanie
Jak widzisz, było trochę konfiguracji ale nie była ona jakoś mocno skomplikowana. Ostatecznie jednak wszystko sprowadza się do uruchomienia komendy yarn deploy
, tak więc na codzień korzystanie z tego rozwiązania jest całkiem wygodne.
Mam nadzieję, że powyższy opis, jak i ten z poprzedniej części na temat migracji bloga będzie dla kogoś przydatny. Tym wpisem kończę temat migracji bloga - czas wrócić do normalności!