Witaj drogi Czytelniku!
To mój pierwszy wpis typu Devlog, w którym będę opisywał zmiany zachodzące na stronie – zarówno te techniczne, jak i dotyczące całej infrastruktury.
Czas ograniczyć koszty
Pierwszą rzeczą, którą postanowiłem zrobić, było ograniczenie wydatków. Utrzymywanie kilku serwerów VPS i wydawanie kilkuset złotych rocznie zaczęło mijać się z celem. Stwierdziłem więc, że warto zrobić porządki – wyrzucić zbędne usługi i zostawić tylko to, co naprawdę potrzebne.
Obecnie infrastruktura wygląda następująco:
- VPS od RackNerd – około 50 zł rocznie,
- VPS Mikr.us Frog – jednorazowe 5 zł,
- Zoho Mail – około 60zł rocznie,
- VPS od Mikr.us – dostałem go w ramach pisania notek,
- oraz TrueNAS hostowany u liske1 🙂
Do czego służą poszczególne serwery?
RackNerd VPS – główny VPS
Serwer VPS od RackNerd pełni rolę hostingu mojego bloga – na którego obecnie patrzysz 🙂
Największą zaletą VPS z RackNerd i głównym powodem, dla którego wybrałem właśnie tę usługę, jest dostęp do publicznego adresu IPv4 w bardzo niskiej cenie. Obecnie coraz trudniej znaleźć sensownego VPS z IPv4 bez dodatkowych opłat, dlatego była to dla mnie istotna przewaga.
Mikr.us Frog
Mały VPS z Mikr.us Frog działa jako serwer SOCKS5 Proxy.
Używam go głównie do tunelowania SSH i wystawienia portu SOCKS5 dla skryptów BASH uruchamianych na głównym serwerze.
W tym przypadku kluczową rolę przy wyborze odegrał model rozliczenia – jednorazowa opłata aktywacyjna w wysokości 5 zł była na tyle niska, że praktycznie przesądziła o decyzji, niezależnie od pozostałych parametrów.
Mikr.us
Mały VPS z Mikr.us jest głównie do pisania notek, oraz host dzięki któremu mam usługę Cytr.us.
Wcześniej był na nim zainstalowany Debian, jednak w celu ujednolicenia infrastruktury został zastąpiony przez Alpine.
Ten VPS, podobnie jak VPS z Mikr.us Frog, nie był serwerem, który brałem pod uwagę jako serwer główny. Powodem takiej decyzji był brak pełnego publicznego adresu IPv4, co w moim przypadku stanowiło wymóg nie do przeskoczenia. Istotny był również brak wirtualizacji KVM – byłem zdany na gotowe obrazy systemów i nie miałem możliwości samodzielnej instalacji systemu od podstaw z poziomu recovery console.
Możliwość rozbudowy infrastruktury
Gdyby pojawiła się potrzeba uruchomienia kolejnego serwera VPS, mam jeszcze jedną sensowną opcję w zanadrzu – TierHive. Oczywiście nie odrzucam również Mikr.us jako serwera, który mógłby zrealizować jakiś mój inny pomysł w przyszłości.
Na szczęście obecnie staram się bardziej upraszczać infrastrukturę niż ją rozbudowywać 😉
Jak to wszystko działa?
Dystrybucja
Wszystkie moje serwery stoją na Alpine Linux
. Krok ten został podyktowany tym, że miałem dość wszystkiego co oparte o systemd, a także potrzebowałem czegoś szybkiego, bez zbędnego bloatware. To tak jak zapytać użytkownika Void Linux bądź Devuan dlaczego wybrał akurat tą dystrybucję.
Firewall
Na serwerze głównym został wygenerowany klucz SSH, którego klucz publiczny został przesłany na serwer Frog. Dzięki temu działa usługa odpowiedzialna za wystawienie tunelu SOCKS5. Przez ten tunel łączy się skrypt pobierający dane z czarnej listy prowadzonej przez Jakuba Mrugalskiego.
Poniżej znajduje się konfiguracja tej usługi odpowiedzialnej za to zadanie (/etc/init.d/ssh-socks):
#!/sbin/openrc-run
name="SSH SOCKS5 tunnel"
description="Persistent SSH SOCKS5 tunnel"
command="/usr/bin/ssh"
command_args="
-N
-D 1080
-p 10105
-o ExitOnForwardFailure=yes
-o ServerAliveInterval=30
-o ServerAliveCountMax=3
[email protected]
"
command_background="true"
pidfile="/run/${RC_SVCNAME}.pid"
supervisor="supervise-daemon"
depend() {
need net
}
Każdego dnia o godzinie 05:00 uruchamiany jest skrypt run_firewall.sh, którego zadaniem jest pobranie adresów IP z powyżej wspomnianej czarnej listy, a następnie dodanie ich do zestawu ipset. Tak przygotowana lista jest później wykorzystywana do blokowania ruchu na serwerze S2.
Poniżej efekt działania skryptu (plik auth.log).
Przed:
Po:
Uwaga: Mam świadomość, że na załączonych zrzutach ekranu widoczne są różne nazwy hostów. Wynika to z faktu, że pochodzą one z dwóch różnych etapów życia tego samego serwera. Historycznie serwer funkcjonował pod nazwą „S2” i był oparty na systemie Ubuntu. Obecnie ten sam serwer działa pod hostname fx.vc-mp.eu oraz został zmigrowany do systemu Alpine Linux.
Strona internetowa – blog
Cała instalacja serwera LAMP została wgrana z pomocą ChatGPT – tak, dobrze czytasz, korzystam z AI. W praktyce posłużyłem się nim jako wsparciem przy konfiguracji MariaDB, Apache2 oraz PHP-FPM, w tym doborze ich parametrów i optymalizacji pod mój przypadek.
Przykładowa konfiguracja:
/etc/apache2/conf.d/mpm.conf
<IfModule mpm_event_module>
StartServers 3
ServerLimit 8
ThreadsPerChild 25
MinSpareThreads 25
MaxSpareThreads 75
MaxRequestWorkers 50
MaxConnectionsPerChild 200
KeepAlive On
KeepAliveTimeout 2
MaxKeepAliveRequests 100
</IfModule>
/etc/apache2/conf.d/php84-module.conf
LoadModule php_module modules/mod_php84.so
DirectoryIndex index.php index.html
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
/etc/my.cnf.d/server.cnf
[mysqld]
innodb_buffer_pool_size = 256M
innodb_buffer_pool_instances = 1
innodb_log_file_size = 64M
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 2
max_connections = 30
thread_cache_size = 8
table_open_cache = 512
tmp_table_size = 32M
max_heap_table_size = 32M
performance_schema = OFF
skip-name-resolve
/etc/php84/php.ini (wyłącznie ważniejsze wartości)
memory_limit = 128M
post_max_size = 32M
upload_max_filesize = 20M
Gotowy skrypt instalujący LAMP na Alpine i go wstępnie konfigurujący dla serwerów Mikr.us (minimalna oferta to: 2.1)
#!/bin/bash
CRON_JOB='0 3 * * * /usr/local/bin/update-sudo-rs.sh >> /var/log/update-sudo-rs.log 2>&1'
cat > /etc/apk/repositories << 'EOF'
https://alpine.sakamoto.pl/alpine/v3.23/main
https://alpine.sakamoto.pl/alpine/v3.23/community
EOF
apk update
apk upgrade
apk add apache2 apache2-ssl apache2-proxy apache2-proxy-html zip unzip php84 php-fpm php-mysqli php-pdo php-pdo_mysql php-session php-json php-mbstring php-openssl php-curl php-xml php-dom php-ctype php-iconv php-simplexml php-redis redis mariadb mariadb-client
apk add linux-pam
apk del sudo
apk add sudo-rs --repository=https://alpine.sakamoto.pl/alpine/edge/community
sed -i 's|listen = 127.0.0.1:9000|listen = /run/php-fpm84/php-fpm.sock|g' /etc/php84/php-fpm.d/www.conf
sed -i 's|;listen.owner = nobody|listen.owner = apache|g' /etc/php84/php-fpm.d/www.conf
sed -i 's|;listen.group = nobody|listen.group = apache|g' /etc/php84/php-fpm.d/www.conf
sed -i 's|;listen.mode = 0660|listen.mode = 0660|g' /etc/php84/php-fpm.d/www.conf
sed -i 's|pm = dynamic|pm = ondemand|g' /etc/php84/php-fpm.d/www.conf
sed -i 's|pm.max_children = 5|pm.max_children = 3|g' /etc/php84/php-fpm.d/www.conf
sed -i 's|;pm.process_idle_timeout = 10s|pm.process_idle_timeout = 10s|g' /etc/php84/php-fpm.d/www.conf
sed -i 's|#LoadModule proxy_module|LoadModule proxy_module|g' /etc/apache2/httpd.conf
sed -i 's|#LoadModule proxy_fcgi_module|LoadModule proxy_fcgi_module|g' /etc/apache2/httpd.conf
sed -i 's|Listen 80|Listen 0.0.0.0:80\nListen [::]:80|g' /etc/apache2/httpd.conf
sed -i -e 's|^#\?bind .*|bind 127.0.0.1|' /etc/redis.conf
cat >> /etc/apache2/conf.d/php-fpm.conf << 'EOF'
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php-fpm84/php-fpm.sock|fcgi://localhost"
</FilesMatch>
DirectoryIndex index.php index.html
EOF
cat > /etc/php84/conf.d/00_opcache.ini << 'EOF'
zend_extension=opcache
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.save_comments=1
opcache.fast_shutdown=1
EOF
rc-service mariadb setup
cat >> /etc/my.cnf.d/low-memory.cnf << 'EOF'
[mysqld]
performance_schema = OFF
innodb_buffer_pool_size = 192M
innodb_log_buffer_size = 2M
query_cache_size = 0
query_cache_type = 0
max_connections = 20
innodb_buffer_pool_instances = 1
innodb_log_file_size = 64M
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 2
thread_cache_size = 8
table_open_cache = 512
tmp_table_size = 32M
max_heap_table_size = 32M
skip-name-resolve
EOF
rc-update add mariadb default
rc-update add php-fpm84 default
rc-update add apache2 default
rc-update add redis default
cat > /usr/local/bin/update-sudo-rs.sh <<'EOF'
#!/bin/sh
apk update \
--repository=https://alpine.sakamoto.pl/alpine/edge/community
apk upgrade -U sudo-rs \
--repository=https://alpine.sakamoto.pl/alpine/edge/community
EOF
chmod +x /usr/local/bin/update-sudo-rs.sh
grep -Fxq "$CRON_JOB" /etc/crontabs/root || echo "$CRON_JOB" >> /etc/crontabs/root
reboot now
Galeria zdjęć
Na TrueNAS uruchomiona jest aplikacja hostująca moją galerię zdjęć. Każde jedno wykonane zdjęcie telefonem przesyłane jest automatycznie do tej aplikacji dzięki czemu mam backup zdjęć i mogę je przeglądać na wielu urządzeniach.
Dzięki serwerowi od RackNerd i dostępnemu publicznemu adresowi IPv4 mogę na domowym serwerze DNS przypisać ten adres do losowej nazwy hosta. W praktyce pozwala mi to wystawić galerię zdjęć w internecie, mimo że fizycznie działa ona w mojej domowej infrastrukturze. W celu zapewnienia bezpiecznego połączenia utworzyłem własny urząd certyfikacji (CA), a następnie wygenerowałem certyfikat SSL wraz z odpowiadającym mu kluczem prywatnym. Certyfikat główny CA został następnie zainstalowany na wszystkich urządzeniach korzystających z usługi, dzięki czemu połączenie jest rozpoznawane jako zaufane.
Dostęp do galerii nie jest wprost publiczny – wymaga użycia skanera lub znajomości sposobu, w jaki została wystawiona. Taki bardziej złożony model dostępu jest celowy i wynika ze względów bezpieczeństwa, aby ograniczyć możliwość przypadkowego odnalezienia usługi.
Całość działa więc jako połączenie:
- lokalnego hostingu na TrueNAS,
- publicznego IPv4 z RackNerd,
- oraz własnej warstwy DNS, która spina to w jedną całość.
W praktyce jest to w pełni produkcyjne środowisko, tylko zaprojektowane w sposób świadomie utrudniający jego odkrycie i dostęp z zewnątrz.
Przywrócenie podstrony z narzędziami
Ten punkt to bardziej chęć pochwalenia się tym, czego używam na co dzień. Skoro inni prezentują swoje narzędzia i workflow, to postanowiłem również pokazać swoje rozwiązania oraz aplikacje, z których korzystam.
Usunięcie podstrony kontaktowej
Podstrona kontaktowa została tymczasowo usunięta ze względów bezpieczeństwa. Wcześniej korzystałem z wtyczki napisanej przez Beherita, jednak z powodu braku jej dalszego utrzymywania uznałem, że najlepszym rozwiązaniem będzie całkowite usunięcie formularza kontaktowego.
Podsumowanie
Udało się zakończyć migrację, uporządkować konfigurację usług i doprowadzić środowisko do stabilnego stanu. W następnych notkach spróbuję opisać moje kolejne eksperymenty z infrastrukturą, nowe wdrożenia, a może i jakiś poradnik – jeżeli będzie taka potrzeba.

