среда, 24 апреля 2024 г.

Docker compose. Запуск контейнеров при старте операционной системы

Воспользуемся systemd для запуска контейнеров при старте системы.

Для этого создадим юнит systemd:
# nano /etc/systemd/system/docker-compose-app.service

Содержимое файла:
[Unit]
Description=Docker Compose Application Service
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/docker compose -f /home/korolev/app-dev/docker-compose.yml up -d
ExecStop=/usr/bin/docker compose -f /home/korolev/app-dev/docker-compose.yml down
TimeoutStartSec=0
Restart=on-failure

[Install]
WantedBy=multi-user.target


В конфигурации предполагается, что разработка идет в директории
/home/korolev/app-dev/

Рестартуем демон systemd:

# systemctl daemon-reload

И запускаем docker compose как службу:
# systemctl start docker-compose-app
# systemctl enable docker-compose-app


воскресенье, 21 апреля 2024 г.

Статические маршруты в NetworkManager. Ошибка Unable to save connection: Cannot modify a connection that has an associated rule- or rule6- file

В новых версия Linux управление сетевой конфигурацией выполняется специальной службой - NetworkManager.
Ранее в старых версиях Linux служба «network» в своей работе опиралась на конфигурационные файлы, расположенные в каталоге
/etc/sysconfig/network-scripts/
Теперь служба «NetworkManager» опирается на конфигурационные файлы в каталоге
/etc/NetworkManager/system-connections/,
а также на скрипты, расположенные в директории
/etc/NetworkManager/dispatcher.d/

Для добавления статического маршрута в NetworkManager можно воспользоваться удобной псевдографической утилитой «nmtui»
# nmtui
После запуска утилиты мы видим меню для работы с сетевыми соединениями. Выбираем «Edit a connection» и нажимаем Enter

В следующем окне выбираем сетевой интерфейс, переходим вправо и выбираем действие «Edit...» (Можно просто выбрать интерфейс и нажать Enter).

Открывается окно редактирования сетевого соединения. Переходим в раздел Routing и в пункт «Edit...»

Откроется дополнительное окно редактирования статических маршрутов. Выбираем «Add...»

Пример заполнения данных о статическом маршруте:

После применения изменений по кнопке «OK», окно редактирования маршрутов должно закрыться и мы должны попасть обратно в панель редактирования настроек интерфейса. Перемещаемся по этому окну до кнопки «OK», нажимаем Enter. Затем в разделе меню выбора интерфейса перемещаемся на кнопку «Back...» и выходим в в основное меню утилиты nmtui. Тут выбираем раздел «Quit» и выходим в консоль.

Для применения настроек нужно выполнить перезапуск службы NetworkManager
# systemctl restart NetworkManager
Статический маршрут пропишется в файл
/etc/NetworkManager/system-connections/<ИМЯ_ИНТЕРФЕЙСА>.nmconnection
Проcмотр правил маршрутизации:
# ip route
default via 10.10.49.254 dev ens18 proto static metric 100
10.10.49.0/24 dev ens18 proto kernel scope link src 10.10.49.166 metric 100
10.200.5.216 via 10.10.49.254 dev ens18 proto static metric 100


Для удаления статического маршрута, нужно опять запустить псевдографический интерфейс nmtui, зайти в раздел конфигурации нужного интерфейса, перейти в раздел «Routing», выбрать не нужный маршрут и применить команду «Remove». После этого выйти из программы nmtuui и перезапустить NetworkManager
# systemctl restart NetworkManager
Из файла /etc/NetworkManager/system-connections/<ИМЯ_ИНТЕРФЕЙСА>.nmconnection
статический маршрут пропадет, но команда ip route по прежнему будет отображать этот удаленный маршрут.
Для того что бы удалить маршрут окончательно нужно или применить команду удаления
# ip route del 10.10.49.0/24 dev ens18
Или перезапустить сетевой интерфейс вот так, чтобы не потерять управление:
# nmcli connection down enp0s3 && nmcli connection up enp0s3
В данном случае enp0s3 – это имя интерфейса.
Ну или можно просто перезагрузиться.

Если в Linux работает старая версия службы управления сетью «network», которая использует скрипты в директории /etc/sysconfig/network-scripts/, в случае добавления статического маршрута через nmtui мы получим ошибку «Unable to save connection: Cannot modify a connection that has an associated ‘rule-’ or ‘rule6-’ file». Это говорит о том, что NetworkManager не может выполнить изменение информации в файлах, лежащих в директории /etc/sysconfig/network-scripts/, а в этих директориях как раз есть что-то нужное для маршрутизации (например, правила маршрутизации в файле /etc/sysconfig/network-scripts/rule-eth1). Данное поведение не считается ошибкой (см https://bugzilla.redhat.com/show_bug.cgi?id=1384799), а говорит о том, что изменение файлов /etc/sysconfig/network-scripts/rule-* и /etc/sysconfig/network-scripts/route-* не поддерживается функционалом NetworkManager.

В этом случае можно пойти следующими путями:

1) Добавлять статические маршруты через редактирование файла
/etc/sysconfig/network-scripts/route-<ИМЯ_ИНТЕРФЕЙСА>
Пример записи в файле:
default via 10.11.90.54 table eth0
10.11.0.0/24 via 10.11.90.16

После изменения файла нужно перезагрузить службу «network»
# systemctl restart network

2) Отказаться от службы «network» и перейти на NetworkManager полностью.
# systemctl stop network
# systemctl disable network
# systemctl enable NetworkManager

В этом случае (в случае использования полностью NetworkManager) так же рекомендуется использовать службу NetworkManager-dispatcher, которая предназначена для отлова событий в системе, таких как падение или поднятие интерфейса. После наступления подобного события, связанного с сетью, благодаря службе NetworkManager-dispatcher будет запускаться скрипт из директории /etc/NetworkManager/dispatcher.d/, связанный с этим событием. Например, после поднятия интерфейса eth2, будет автоматически монтироваться шара по NFC.
Запуск службы NetworkManager-dispatcher:
# systemctl start NetworkManager-dispatcher
# systemctl enable NetworkManager-dispatcher

У NetworkManager-dispatcher есть расширение NetworkManager-dispatcher-routing-rules, которое может использовать в скриптах диспетчера старые правила описанные в файлах
/etc/sysconfig/network-scripts/route-*
/etc/sysconfig/network-scripts/rule-*
и прописывать маршруты на основе этих правил при событиях up и down интерфейсов
Установка этого расширения:
# yum makecache
# yum -y install NetworkManager-dispatcher-routing-rules





пятница, 12 апреля 2024 г.

Решение проблемы сбойного триггера в базе Mariadb. Trigger does not exist

Есть база данных Mariadb.
При бэкапе базы я получаю ошибку триггера:
mysqldump: Couldn't execute 'SHOW CREATE TRIGGER `orders_after_delete`': Trigger does not exist (1360)
При дампе таблиц базы поочередно, все же удается сохранить дамп базы частями, но при развертывании таблицы order опять фикируем ошибку:
ERROR 1359 (HY000) at line 13313: Trigger 'al.orders_after_delete' already exists

Подключаюсь к основной базе и командой SHOW TRIGGERS; смотрю, что такой триггер есть orders_after_delete.
Хочу его пересоздать (удалить и добавить заново), но при удалении триггера, его как буд-то нет:
MariaDB [al]> DROP TRIGGER orders_after_delete;
ERROR 1360 (HY000): Trigger does not exist


Проверка базы на ошибки проходит успешно, но триггер все равно висит у таблицы orders и не удаляется ни через консоль, ни через phpmyadmin.
# mysqlcheck -u**** -p -r al orders
al.orders OK


РЕШЕНИЕ ПРОБЛЕМЫ:

1) Необходимо создать одноименный триггер из консоли mariadb для этой же таблицы
При этом SHOW TRIGGERS будет показывать два триггера orders_after_delete.
Но зато команда выполнения дампа базы и восстановления базы будут идти без сбоев.

2) В час наименьшей нагрузки выполняем удаление сбойного триггера так:

2.1) Подключаемся к базе

# mysql -hlocalhost --user <ПОЛЬЗОВАТЕЛЬ> -p<ПАРОЛЬ>
>USE al;

2.2) Вводим команду удаления триггера:
DROP TRIGGER orders_after_delete;
Удаляется при этом один триггер (который создали ранее для решения проблемы создания дампа), а второй сбойный триггер остается в системе.
Попытки его удалить вновь не удачные:
MariaDB [al]> DROP TRIGGER orders_after_delete;
ERROR 1360 (HY000): Trigger does not exist

2.3) Переименовываем таблицу orders
MariaDB [al]> ALTER TABLE orders RENAME TO orders2;
Query OK, 0 rows affected (0.07 sec)


2.4) Удаляем сбойный триггер. Теперь он удаляется без проблем:
MariaDB [al]> DROP TRIGGER orders_after_delete;
Query OK, 0 rows affected (0.01 sec)


2.5) Возвращаем таблице исходное название:
MariaDB [al]> ALTER TABLE orders2 RENAME TO orders;
Query OK, 0 rows affected (0.00 sec)


2.6) Создаем триггер

Теперь в системе отображается один триггер, дампы выполняются успешно, восстановление - тоже.

понедельник, 8 апреля 2024 г.

Изменение пользовательского приветствия (banner) при подключении к консоли Linux.

При подключении к серверу по SSH пользователю можно выводить приветствие.
Приветствие бывает:
1) Перед вводом логина и пароля
2) После успешной авторизации

Добавление пользовательского приветствия перед авторизацией.
Нужно создать файл приветствия. Например: /etc/login.warn
# touch /etc/login.warn
Открываем файл и вписываем туда приветствие
# nano /etc/login.warn
Пример:
 *** Тестовый сервер Королева В.С. ***
 *** Самарская область, г. Самара ***
 *** Необходима авторизация ***

Теперь данный файл /etc/login.warn необходимо прописать в настройках SSH.
Находим строки:
# no default banner path
#Banner none

Прописываем путь до файла приветствия:
Banner /etc/login.warn
Перезапускаем демон SSHD
# systemctl restart sshd

Добавление приветствия после авторизации:
Приветствие после авторизации прописывается в файле /etc/motd
motd - это сокращение от Message Of The Day (Приветствие дня).
# nano /etc/motd
Пример текста:
*** Добро пожаловать ***
Теперь при авторизации будут выдаваться приглашения:
$ ssh korolev@10.10.49.166
*** Тестовый сервер Королева В.С. ***
*** Самарская область, г. Самара ***
*** Необходима авторизация ***
korolev@10.10.49.166's password:
*** Добро пожаловать ***
Web console: https://KVS:9090/ or https://10.10.49.166:9090/
Last login: Sun Apr 7 11:53:30 2024 from 10.10.49.252








понедельник, 1 апреля 2024 г.

Установка web-сервера nginx с отдельными модулями php и mariadb с помощью docker

Создаем главную директорию проекта, где будут сложены все необходимые файлы и базы
# mkdir web-server
# cd web-server/

Создаем директории для проекта.
Под каждый контейнер своя директория для конфигов, и плюс директория data - для файлов и datadb для файлов базы данных.
# mkdir nginx
# mkdir php-fpm
# mkdir mariadb
# mkdir data
# mkdir datadb


Скачиваем образ nginx и php-fpm
# docker pull nginx
# docker pull bitnami/php-fpm
# docker pull mariadb


В директорию data кладем тестовые файлы index.html (любая страничка) и info.php (с функцией phpinfo();)
Пример файла index.html
<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Заголовок</title>
  <link rel="stylesheet" href="./styles/style.css">
</head>
<body>
  <header>
    <h1>Сайт-тест HTTP NGINX-1</h1>
    <p>Просто какая-то информация</p>
  </header>
  <main>
     12345 АБВГДЕ, Основной блок информации
   </main>
   <footer>
       <p>footer (подвал), нижняя часть сайта</p>
   </footer>
    <!--подключение скриптов <script src="app.js"></script> -->
</body>
</html>


Содержимое файла info.php
<?php
    phpinfo();
?>


Нам необходимо выстроить конструкцию контейнеров, показанную на рисунке ниже

Основная папка с файлами проекта – это папка data, куда будем складывать файлы html и php.

В директорию nginx кладем типовой файл nginx.conf конфига nginx.

Главное, что нужно указать, это параметры директивы fastcgi_pass в блоке location ~ \.php$:
fastcgi_pass php-fpm:9000;
Директива fastcgi_pass задаёт адрес FastCGI-сервера, которому сервер nginx будет передавать php файлы для обработки. Тут указывается доменное имя и порт. 9000 – это стандартный порт, на котором работает сервер php-fpm. А доменное имя «php-fpm» - это доменное имя сервера php-fpm. В системе doker при старте контейнеров в каждом контейнере автоматически пропишутся все имена запущенных сервисов. И если будет запущен контейнер сервиса «php-fpm», то nginx будет знать его внутренний адрес по имени «php-fpm».
Второй важный момент – это директива
fastcgi_param SCRIPT_FILENAME /app$fastcgi_script_name;
Тут необходимо указать правильный путь до местонахождения файлов php. В контейнере, созданном на основе образа bitnami/php-fpm, это будет папка /app. Поэтому переменная SCRIPT_FILENAME должна начинаться с «/app».
В конфигурационном файле важные изменяемые значения выделены красным.
worker_processes 1;
pid /run/nginx.pid;
events {
  worker_connections 1024;
}
http {
    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;
    
    server {
        listen 80;
        server_name localhost;
        root /usr/share/nginx/html;
        error_log /var/log/nginx/error.log;
        access_log /var/log/nginx/access.log;
        location / {
             index index.php index.html index.htm;
        }
        location ~ \.php$ {
            fastcgi_pass php-fpm:9000;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME /app$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
        }
    }
}

Директива «error_log /var/log/nginx/error.log;» определяет, куда сервер nginx будет складывать логи ошибок. Контейнер bitnami/php-fpm по умолчанию настроен так, что логи для записи передает для записи web-серверу, в данном случае nginx. И искать логи ошибок php нужно тоже в этом же файле /var/log/nginx/error.log.

Создаем файл docker-compose.yml, который описывает контейнеры nginx и php-fpm.
# touch web-server/docker-compose.yml
version: '3'
services:
    nginx:
        image: nginx
        container_name: nginx1
        ports:
            - 8080:80
        volumes:
            - ./data:/usr/share/nginx/html
            - ./nginx/nginx.conf:/etc/nginx/nginx.conf
            - ./nginx/logs:/var/log/nginx/
        depends_on:
            - php-fpm
        networks:
            - internal
    
    php-fpm:
        image: bitnami/php-fpm
        container_name: php1
        volumes:
            - ./data:/app
         networks:
             - internal
    
networks:
    internal:
        driver: bridge
Из директории web-server запускаем контейнеры
# docker compose up --build
Параметр --build означает, что перед запуском контейнеров произойдет их сборка.

Теперь можно обратиться к web-серверу и прочитать тестовые файлы:
http://<IPадрес>:8080/
http://10.10.49.166:8080/info.php

Останавливаем контейнеры, нажав CTRL+C в командной строке, где запускали контейнеры (docker compose up --build)

Теперь запускаем контейнеры вместе с контейнером Mariadb. Для этого в docker-compose.yml добавляем новый сервис, работающий в новом контейнере:
    mariadb:
        image: mariadb
        container_name: mariadb1
        environment:
            MYSQL_ROOT_PASSWORD: rootpasswd
            MYSQL_DATABASE: mydb
            MYSQL_USER: user1
            MYSQL_PASSWORD: secret1
        command: mariadbd --log_error="/var/log/mysql/error.log"
        ports:
            - 3309:3306
        volumes:
            - ./datadb:/var/lib/mysql
            - ./mariadb/error.log:/var/log/mysql/error.log
        networks:
            - internal

Директива MYSQL_ROOT_PASSWORD: rootpasswd определить пароль для root пользователя базы данных SQL. Директивы MYSQL_DATABASE: mydb создаст базу данных mydb. Создание базы выполняется командой CREATE DATABASE IF NOT EXIST, поэтому если база mydb уже есть, она не будет создана снова.
Директивы MYSQL_USER: user1 и MYSQL_PASSWORD: secret1 создадут пользователя user1 с паролем secrtet1 и сделают его администратором базы, заданной в MYSQL_DATABASE.
Регистрация ошибок контейнера mariadb осуществляется по умолчанию через системный journal. Для того, что бы запись ошибок велась в отдельный файл, нужно в контейнере
в файле /etc/mysql/mariadb.conf.d/50-server.cnf раскомментировать строку:
log_error = /var/log/mysql/error.log. Но при пересборке контейнера это изменение потеряется. Поэтому пойдем другим путем. Создадим в основной системе файл для логов и разрешим писать туда пользователю из docker контейнера:
# touch mariadb/error.log
# chmod 777 mariadb/error.log

В секции command: пропишем команду, которая заставит демон mariadb включить запись лого в файл: mariadbd --log_error="/var/log/mysql/error.log"
И затем в секции volumes примонтируем внутренний файл контейнера «/var/log/mysql/error.log» к файлу основной операционной системы «mariadb/error.log»
Теперь логи mariadb будут записываться в файл в основной операционной системе.

После редактирования файла docker-compose.yml запускаем контейнеры:
# docker compose up --build
Из другой консоли пытаемся подключиться к базе:
# /usr/bin/mariadb -h127.0.0.1 --port=3309 -uroot –p
Вводим пароль из директивы MYSQL_ROOT_PASSWORD и мы в консоли mariadb под рутом.
Можно так же подключиться под именем user1 и паролем secret1
# /usr/bin/mariadb -h127.0.0.1 --port=3309 -u user1 -psecret1
Проверяем, создалась ли база mydb?
MariaDB [(none)]> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mydb |
+--------------------+
2 rows in set (0.001 sec)
Все в порядке, база есть.
Создадим простую таблицу и внесем туда одну строку для теста
> USE mydb;
> CREATE TABLE table_test (a int, b varchar(10));
> INSERT table_test VALUES(1,'mytext');

Эта информация останется в базе даже после перезагрузки контейнеров, так как файлы базы подключаются при старте контейнера из основной системы

Создадим файл dbtest.php в папке data, который будет подключаться к базе данных и выводить данные из таблицу table_test
<?php
    $mysqli = new mysqli("mariadb", "user1", "secret1", "mydb");
    if (mysqli_connect_errno()) {
        echo "Ошибка: ", mysqli_connect_error();
        exit;
    } else {
        echo "Соединение установлено<br>";
    }
   $query="SELECT a, b FROM table_test ";
   $result = $mysqli->query($query) or die("Ошибка запроса");
   echo "Данные из базы:<br>";
   while($z=$result->fetch_row()) {
       echo "a: ".$z[0].", b: ".$z[1];
   }
?>

В качестве имени сервера, к которому нужно обращаться в php указываем имя сервиса из описания контейнера в файле docker-compose.yml.
$mysqli = new mysqli("mariadb", "user1", "secret1", "mydb");
Теперь обратившись по URL мы увидим информацию из базы данных:
http://10.10.49.166:8080/dbtest.php

Следующие запуски можно выполнять так

# docker compose up
Запуск в режиме демона:
# docker compose up –d

Остановка контейнеров:

# docker stop mariadb1

Запуск контейнера:

# docker start mariadb1

Остановка всех контейнеров:

# docker compose down

Просмотр состояния контейнеров

# docker compose ps

Вход в контейнеры:

# docker exec -it nginx1 /bin/bash
# docker exec -it php1 /bin/bash
# docker exec -it mariadb1 /bin/bash


Просмотр логов контейнеров:
# docker compose logs -f nginx
# docker compose logs -f mariadb
# docker compose logs -f php-fpm