Jak Ansiblem zainstalować nginx’a i nie tylko
Administrator to jednak leniwa bestia, która lubi ułatwiać sobie życie i dla wszystkiego, co można zautomatyzować, użyje wszelkich możliwych metod, aby zautomatyzowane było. Ten, który sam siebie zwykł określać się jako „Born to be root” zdecydowanie twierdzi, że nie ma klepania tych wszystkich zaklęć, które byłby musiał napisać no i dodatkowo unika się błędów, które przy tej pisaninie można popełnić.
Oczywiście są różne sposoby automatyzacji, jednak w tym artykule skupimy się na skorzystaniu z dobrodziejstw jaki daje nam Ansible.
Co to jest Ansible?
Nie będę tutaj się rozwodził nad genezą pochodzenia i w których książkach wystąpiło to słowo (ten, kto czytał „Grę” Endera, ten wie – bo to jedna z nich), które stało się inspiracją dla autora. Wypada jednak w kilku żołnierskich słowach określić do czego to cudo służy zanim przejdziemy do clue naszego tematu.
Ogólnie Ansible służy do komunikacji i zdalnego wywoływania poleceń, skryptów na zasadzie Control Node → Manage Node, gdzie Control Node jest stacją na której odpalamy nasz wynalazek ergo to co chcemy osiągnąć, a Manage Node jest komputerem do którego chcemy się połączyć i wykonać to co w tymże wynalazku zawarliśmy. Innymi słowy Ansible jest narzędziem służącym do zarządzania stanem infrastruktury.
Pozwala zautomatyzować administrację konfiguracją serwerów, dostarczanie aplikacji oraz wiele innych zadań, które administrator musi wykonać. Świetne jest to, że Ansible nie potrzebuje agenta, a komunikacja odbywa się po protokole SSH czyli podstawowym jaki w świecie serwerowym egzystuje.
Wspomnę jeszcze o kilku charakterystycznych dla Ansible rzeczach:
- ma mnóstwo modułów gotowych do wykorzystania
- można uruchamiać komendy ad hoc lub też pisząc zestawy poleceń w postaci playbooków w języku YAML
- definicje serwerów z którymi będziemy się komunikować definiujemy w plikach inventory
Jeżeli ktoś chciałby się zagłębić w świat Ansible niech zapyta w Internecie, bo materiałów jest mnóstwo, a informacji w nich zawartych starczy na wiele długich wieczorów.
W tym cyklu jednak interesuje nas to w jaki sposób – taki z życia wzięty – można wykorzystać twór nad którym się pochylamy. Tutaj wykorzystamy zarówno to co opisywałem w cyklu Shell Scripting w przykładach: jak w szybki sposób wygenerować certyfikaty SSL dla serwerów oraz po części #AdminCases, czyli gotowa solucja: jak stworzyć certyfikaty SSL dla serwera bez udziału komercyjnego CA, aby przeglądarki uważały, że jestem zaufany. Zatem jak zwykle określmy jaki przyświeca nam cel, który na końcu ma być osiągnięty:
- chcemy automatycznie, na różnych platformach Linux zainstalować serwer Nginx
- przekazać wygenerowany klucz i certyfikat podpisany własmym CA na serwer
- zmienić konfigurację serwera WWW tak aby skorzystał z SSL z naszymi danymi
- uruchomić serwer www i sprawdzić czy działa
Na potrzeby tego przykładu utworzyłem sobie 4 kontenery Linux (LXC), aby pokazać wieloplatformowość tego rozwiązania i zweryfikować w jaki sposób uzależnić dane wykonanie od platformy. Do dyspozycji mamy:
- CentOS 7
- CentOS 8
- Debian 11
- Ubuntu 20
Zaczynamy od stworzenia pliku inventory z definicją naszych serwerów. Pozwolicie, że nie będę się rozwodził nad tym co i w jaki sposób można zamieszczać w tymże pliku ponieważ jest to dobrze opisane na stronach Ansible chociażby tutaj. Tutaj mamy konkretne zadanie do zrealizowania, a poza tym zawsze wychodziłem z założenia, że przykłady uczą dużo więcej, dają więcej frajdy oraz wiedzy aniżeli dywagowanie czysto teoretyczne.
Podstawowy plik inventory znajduje się w katalogu /etc/ansible
i nazywa się hosts
. Utworzymy sobie grupę webservers z wpisami określającymi nasze 4 serwery:
[webservers]
ubuntu20 ansible_host=10.0.3.248
debian11 ansible_host=10.0.3.196
centos7 ansible_host=10.0.3.224
centos8 ansible_host=10.0.3.45
Jako, że będziemy się z nimi komunikować po protokole SSH powinniśmy przekazać klucze użytkownika. Z racji tego, że to konfiguracja testowa, będę się łączył z Manage Node poprzez użytkownika root. Ważne! Na serwerach produkcyjnych należy używać sudo, co postaram się pokazać w najbliższej przyszłości. Najprostszym sposobem na przekazanie kluczy jest:
ssh-copy-id -i /home/aciek/.ssh/id_rsa.pub [email protected]
Jeżeli już się to udało to powinniśmy mieć możliwość zalogowania się na dane serwery. Jedno z głowy. Teraz należy przygotować klucze i certyfikaty dla serwerów. W jaki sposób to wykonać zostało opisane we wspomnianym przeze mnie artykule z cyklu Shell Scripting w przykładach.
Następnie musimy utworzyć sobie katalog, w którym będziemy pracować, a w tym katalogu niezbędne nam będą jeszcze dwa kolejne: templates oraz certs. Do katalogu certs wkopiujmy utworzone przez nas klucze i certyfikaty. Dla przykładu:
rw-rw-r-- 1 aciek aciek 1700 Jul 19 01:30 debian11.crt
-rw------- 1 aciek aciek 1675 Jul 19 01:30 debian11.key
-rw------- 1 aciek aciek 1679 Jul 19 01:30 ubuntu20.key
-rw-rw-r-- 1 aciek aciek 1700 Jul 19 01:30 ubuntu20.crt
-rw-rw-r-- 1 aciek aciek 1700 Jul 19 01:30 centos8.crt
-rw-rw-r-- 1 aciek aciek 1700 Jul 19 01:30 centos7.crt
-rw------- 1 aciek aciek 1675 Jul 19 01:30 centos8.key
-rw------- 1 aciek aciek 1679 Jul 19 01:30 centos7.key
W katalogu templates utworzymy nasze konfiguracje w podziale na rodziny systemów operacyjnych, które będziemy konfigurować:
- Red Hat- do których należą: RHEL, CentOS czy Oracle Linux
- Debian– do których należą: Debian oraz Ubuntu
W Ansible będziemy korzystać z modułu templates, który służy do wymiany konfiguracji gdzie w samym pliku template możemy użyć zmiennych, które będą pochodziły z Ansible lub też będą zdefiniowane w YAML czyli naszym playbooku. W przypadku rodziny Red Hat najłatwiej nasz template stworzyć na bazie nginx.conf
(dla przykładu można wziąć plik z serwera z tej rodziny, gdzie mamy zainstalowany nginx
). Taki template w moim przypadku nosi nazwę redhat_nginx.conf
i ma następującą strukturę:
# For more information on configuration, see:
# * Official English Documentation: nginx.org/en/docs/
# * Official Russian Documentation: nginx.org/ru/docs/
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 4096;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See nginx.org/en/docs/ngx_core_module.html
# for more information.
include /etc/nginx/conf.d/*.conf;
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
# Settings for a TLS enabled server.
#
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name _;
root /usr/share/nginx/html;
ssl_certificate "/etc/nginx/ssl/{{ ansible_hostname }}.crt";
ssl_certificate_key "/etc/nginx/ssl/{{ ansible_hostname }}.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
Należy zwrócić uwagę na użycie zmiennej ansible_hostname, która pochodzi bezpośrednio z Ansible i jest nazwą serwera do którego będziemy się łączyć. W trakcie wykonania playbook’a zostanie ona zamieniona na nazwę właściwą serwerowi, na którym będzie wykonana dlatego klucz i certyfikat powinny nosić nazwę serwera.
Przejdźmy teraz do rodziny Debian. Tam konfiguracja Nginx jest nieco inna i definicja sekcji server, gdzie ładujemy definicji związane z SSL są w katalogu /etc/nginx/sites-available
w pliku default
, który aby był widoczny jako aktywny jest podlinkowany do /etc/nginx/sites-enabled. Konfiguracja w przypadku tej rodziny serwerów wygląda następująco:
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# www.nginx.com/resources/wiki/start/
# www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##
# Default server configuration
#
server {
listen 80 default_server;
listen [::]:80 default_server;
# SSL configuration
#
# listen 443 ssl default_server;
# listen [::]:443 ssl default_server;
#
# Note: You should disable gzip for SSL traffic.
# See: bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
# pass PHP scripts to FastCGI server
#
#location ~ \.php$ {
# include snippets/fastcgi-php.conf;
#
# # With php-fpm (or other unix sockets):
# fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
# # With php-cgi (or other tcp sockets):
# fastcgi_pass 127.0.0.1:9000;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
server {
# SSL configuration
#
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
#
# Note: You should disable gzip for SSL traffic.
# See: bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
ssl_certificate "/etc/nginx/ssl/{{ ansible_hostname }}.crt";
ssl_certificate_key "/etc/nginx/ssl/{{ ansible_hostname }}.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
# pass PHP scripts to FastCGI server
#
#location ~ \.php$ {
# include snippets/fastcgi-php.conf;
#
# # With php-fpm (or other unix sockets):
# fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
# # With php-cgi (or other tcp sockets):
# fastcgi_pass 127.0.0.1:9000;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
# listen 80;
# listen [::]:80;
#
# server_name example.com;
#
# root /var/www/example.com;
# index index.html;
#
# location / {
# try_files $uri $uri/ =404;
# }
Uff. Skończyliśmy tworzyć wszystkie niezbędne oboczności więc w końcu możemy zabrać się za kreowanie YAML, czyli de facto definicję wszystkich rzeczy, które na serwerach mają się wydarzyć. Pragnę zwrócić uwagę na składnię YAML, która bywa wredna ponieważ ważne są wcięcia.
Dlatego, Drogi Czytelniku, poświęć chwilę na to, aby zapoznać się jak korzystać z dobrodzejstw tego języka i na co zwrócić szczególną uwagę. Sam nie będę poruszał tego wątku w niniejszym artykule, bo skupiamy się na tym co mamy do osiągnięcia.
Przejdźmy do definicji playbook’a. Może zrobię to w następujący sposób: umieszczę jego treść w całości, a następnie omówię co się dzieje w poszczególnych etapach.
- hosts: webservers
remote_user: root
tasks:
- name: Packages gather info
package_facts:
manager: auto
- name: Check nginx installed on OS
debug:
msg: "Nginx not installed!"
when: "'nginx' not in ansible_facts.packages"
- name: Install latest Nginx on RedHat Family OS
yum:
name: nginx
state: latest
when: "'nginx' not in ansible_facts.packages and ansible_os_family == 'RedHat'"
- name: Install latest Nginx on Debian Family OS
apt:
name: nginx
state: latest
when: "'nginx' not in ansible_facts.packages and ansible_os_family == 'Debian'"
- name: Packages gather info
package_facts:
manager: auto
- name: Make directory for SSL
file:
path: /etc/nginx/ssl
state: directory
mode: 0755
when: "'nginx' in ansible_facts.packages"
- name: Copy server key
copy:
src: ./certs/{{ ansible_hostname }}.key
dest: /etc/nginx/ssl/{{ ansible_hostname }}.key
owner: root
group: root
mode: 0644
when: "'nginx' in ansible_facts.packages"
- name: Copy server cert
copy:
src: ./certs/{{ ansible_hostname }}.crt
dest: /etc/nginx/ssl/{{ ansible_hostname }}.crt
owner: root
group: root
mode: 0644
when: "'nginx' in ansible_facts.packages"
- name: Nginx config from template for RedHat Family OS
template:
src: templates/redhat_nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: 0644
backup: yes
when: "'nginx' in ansible_facts.packages and ansible_os_family == 'RedHat'"
- name: Nginx config from template for Debian Family OS
template:
src: templates/debian_default
dest: /etc/nginx/sites-available/default
owner: root
group: root
mode: 0644
backup: yes
when: "'nginx' in ansible_facts.packages and ansible_os_family == 'Debian'"
- name: Restart Nginx
systemd:
name: nginx
Zaczynamy od definicji hostów, na których ma się wykonać nasz playbook Ansible, czyli to co zdefiniowaliśmy w inventory. W tym przypadku będzie to wykonanie na 4 hostach, ale równie dobrze możemy zamiast użycia nazwy grupy zmienić to na zapis:
hosts: centos7, ubuntu20
wtedy wykona się tylko na 2 wskazanych hostach. W każdym razie hosty, na powinny znajdować się w pliku inventory (ten w etc jest plikiem defaultowym)
Możemy dla przykładu użyć zapisu:
hosts: webservers, !ubuntu20
Oznacza to, że nasz playbook wykona się na wszystkich serwerach w grupie webservers
poza serwerem ubuntu20
.
Następnie mamy wskazanie remote_user
czyli jakim użytkownikiem będziemy uruchamiać polecenia – w naszym przypadku root. Możemy w końcu przejść do tego co właściwie ma się stać czyli tasks. Aby wszystko działało prawidłowo na serwerach z rodziny Debian powinien zostać zainstalowany pakiet python3-apt
, a na CentOS 7 należy doinstalować epel czyli Extra Package for Enterprise Linux.
Dobra, to lecimy z tym koksem krok po kroku.
- Etap pierwszy:Packages gather info – zbieramy informacje na temat zainstalowanych pakietów na serwerze wykorzystując moduł
package_facts
. Informacje zebrane są przetrzymywane i będziemy z nich korzystać w kolejnych etapach. - Etap drugi:
Check nginx install on OS
– wyświetlamy na ekranie jeżeli pakietnginx
nie jest zainstalowany na danym serwerze. Korzystamy w tym przypadku z modułu debug. - Etap trzeci:Install latest Nginx on RedHat Family OS – w tym etapie skorzystamy z informacji zebranych w pierszym etapie, ponieważ to zadanie zostanie wykonane w przypadku, gdy nie jest zainstalowany nginx. Dodatkowo sprawdzamy, że serwer jest pochodzenia RedHat, ponieważ będziemy korzystali z modułu yum, czyli podstawowego managera pakietów dla tej rodziny. State latest oznacza instalację najnowszego pakietu
- Etap czwarty:Install latest Nginx on Debian Family OS – podobnie jak w etapie trzecim sprawdzamy, czy jest zainstalowany pakiet nginx. W tym przypadku dotyczy to rodziny serwerów Debian. Jeżeli nie ma zainstalowanego pakietu korzystając z modułu apt – czyli podstawowego managera pakietów dla Debiana jest wykonywana instalacja najnowszego pakietu.
- Etap piąty:Packages gather info – aktualizacja informacji o zainstalowanych pakietach, która będzie niezbędna w kolejnych etapach.
- Etap szósty:Make directory for SSL – korzystając z modułu Ansible File tworzymy katalog we wskazanej ścieżce z odpowiednim modem w przypadku, gdy pakiet nginx jest zainstalowany.
- Etap siódmy:Copy server key – kopiowanie klucza serwera. Tutaj z kolei korzystamy z modułu Copy, który jak można się domyślić służy do kopiowania. Może być to kopiowanie z serwera Control Node na Manage Node lub w ramach kopiowania remote na Manage Node. W naszym przypadku mamy tą pierwszą opcję. Dodatkowo korzystamy ze zmiennej
ansible_hostname
, która jest przekazywana jako nazwa pliku do klucza serwera, który generowaliśmy. - Etap ósmy:jest analogiczny do etapu siódmego z tym, że kopiowany jest plik z certyfikatem.
- Etap dziewiąty:Nginx config from template for RedHat Family OS – tutaj korzystamy z modułu template i przekazujemy wskazany plik template, który wcześniej utworzyliśmy dla danej rodziny serwerów. Jak wcześniej wspominałem następuje zamiana zmiennej ansible_hostname w template na nazwę serwera. Ustawiane są również uprawnienia do pliku.
- Etap dziesiąty– analogiczny do dziewiątego z tym, że dotyczy Debian OS Family.
- Etap jedenasty– korzystając z modułu systemd restartujemy serwis nginx.
Dużo się dzieje prawda? A właściwie to będzie się dziać, bo przecież dopiero zdefiniowaliśmy co ma być robione. Przed nami uruchomienie naszego wynalazku, a zatem do dzieła.
aciek@nielot-lenovo:~/aciek/AnsibleScripts/NginxSSLInstall$ ansible-playbook nginx_install_with_ssl.yaml
PLAY [webservers] ****************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************************************************************************************************************************************
ok: [ubuntu20]
[WARNING]: Platform linux on host debian11 is using the discovered Python interpreter at /usr/bin/python3, but future installation of another Python interpreter could change this. See
docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
ok: [debian11]
ok: [centos7]
ok: [centos8]
TASK [Packages gather info] ******************************************************************************************************************************************************************************************************************
ok: [centos8]
ok: [debian11]
ok: [centos7]
ok: [ubuntu20]
TASK [Check nginx installed on OS] ***********************************************************************************************************************************************************************************************************
ok: [ubuntu20] => {
"msg": "Nginx not installed!"
}
ok: [debian11] => {
"msg": "Nginx not installed!"
}
ok: [centos7] => {
"msg": "Nginx not installed!"
}
ok: [centos8] => {
"msg": "Nginx not installed!"
}
TASK [Install latest Nginx on RedHat Family OS] **********************************************************************************************************************************************************************************************
skipping: [ubuntu20]
skipping: [debian11]
changed: [centos8]
changed: [centos7]
TASK [Install latest Nginx on Debian Family OS] **********************************************************************************************************************************************************************************************
skipping: [centos7]
skipping: [centos8]
changed: [ubuntu20]
changed: [debian11]
TASK [Packages gather info] ******************************************************************************************************************************************************************************************************************
ok: [centos7]
ok: [centos8]
ok: [debian11]
ok: [ubuntu20]
TASK [Make directory for SSL] ****************************************************************************************************************************************************************************************************************
changed: [ubuntu20]
changed: [debian11]
changed: [centos7]
changed: [centos8]
TASK [Copy server key] ***********************************************************************************************************************************************************************************************************************
changed: [ubuntu20]
changed: [debian11]
changed: [centos7]
changed: [centos8]
TASK [Copy server cert] **********************************************************************************************************************************************************************************************************************
changed: [ubuntu20]
changed: [centos7]
changed: [centos8]
changed: [debian11]
TASK [Nginx config from template for RedHat Family OS] ***************************************************************************************************************************************************************************************
skipping: [ubuntu20]
skipping: [debian11]
changed: [centos7]
changed: [centos8]
TASK [Nginx config from template for Debian Family OS] ***************************************************************************************************************************************************************************************
skipping: [centos7]
skipping: [centos8]
changed: [debian11]
changed: [ubuntu20]
TASK [Restart Nginx] *************************************************************************************************************************************************************************************************************************
changed: [centos7]
changed: [ubuntu20]
changed: [debian11]
changed: [centos8]
PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
centos7 : ok=10 changed=6 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
centos8 : ok=10 changed=6 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
debian11 : ok=10 changed=6 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
Ta-dam!!!! No wszystko się udało i to z bardzo dobrym skutkiem, sądząc po wyniku wypluwanym przez Ansible. Sprawdźmy, czy działa tak jak chcieliśmy. Należy pamiętać, że aby przeglądarka internetowa nie czepiała się o zaufaniu wystawionego certyfikatu ten musi być zaimportowany do zaufanych urzędów certyfikacji tak jak opisywałem w poprzednich artykułach. Sprawdźmy zatem, czy się udało.
Bosko. Wszystko co chcieliśmy osiągnąć powiodło się i możemy spokojnie powiedzieć, że zapanowaliśmy nad materią Ansible. Mam nadzieję, że przykład się podobał i przyda się w życiu. Czego życzę ja i Mieczysław wraz z rodziną (dla tych z młodszego pokolenia to cytat z „Koncertu życzeń”, a to a propos jakbyście mieli jakieś życzenia, czy pytania to służę pomocą, o ile jeszcze szara substancja na to pozwoli). Piszcie w komentarzach i powodzenia przy tworzeniu własnych playbooków :)