В статье я расскажу, как можно устанавливать пакеты для python с помощью pip, если на этом сервере нет интернета. Поможет нам в этом DevPI.
Теория
Когда вам нужно установить дополнительный модуль для python, то вы обычно выполняете команду pip install <имя модуля>.
pip — это установщик пакетов для python. Он их скачивает из определённого репозитория и устанавливает.
Репозитории для python называют индексами и по умолчанию pip использует индекс PyPI (Python Package Index). Но для pip можно указать и другой индекс.
В этой статье мы познакомимся с инструментом DevPI (github), который позволяет создать свой индекс (root/pypi), который синхронизован с индексом PyPI. Документация по этому инструменту доступна здесь.
Постановка задачи
В этой статье будем реализовывать следующую схему:

На рисунке выше, сервера Server2 и Server3 не имеют выхода в интернет, зато у них есть доступ к серверу Server1.
Server1 имеет доступ в интернет, поэтому на нём можно установить программу DevPI. С его помощью можно создать свой индекс (root/pypi) и синхронизировать его с индексом PyPI.
Server2 и Server3 смогут устанавливать пакеты с помощью pip из индекса, который мы создадим на Server1.
Практика
У меня все сервера на Debian 10.
В самом начале у меня есть интернет на всех серверах. Это необходимо, чтобы установить pip. Для python3 это пакет python3-pip. Его нужно поставить и на всех трёх серверах.
# apt install python3-pip
Теперь можете отключить интернет на серверах Server2 и Server3. Я просто удаляю на них шлюз по умолчанию.
Следующие действия будем выполнять на Server1 (на нём интернет остался).
Сервер devpi требует установки libffi-dev, установим его. Затем используя pip установим devpi-server и devpi-web.
# apt install libffi-dev # pip3 install -U devpi-server # pip3 install -U devpi-web
Дальше вам нужно добавить в переменную PATH путь /usr/local/bin. Туда были установлены следующие утилиты:
# ls -l /usr/local/bin | grep devpi -rwxr-xr-x 1 root root 237 Apr 19 13:15 devpi-clear-search-index -rwxr-xr-x 1 root root 231 Apr 19 13:12 devpi-export -rwxr-xr-x 1 root root 219 Apr 19 13:12 devpi-fsck -rwxr-xr-x 1 root root 234 Apr 19 13:12 devpi-gen-config -rwxr-xr-x 1 root root 231 Apr 19 13:12 devpi-gen-secret -rwxr-xr-x 1 root root 233 Apr 19 13:12 devpi-import -rwxr-xr-x 1 root root 219 Apr 19 13:12 devpi-init -rwxr-xr-x 1 root root 225 Apr 19 13:12 devpi-passwd -rwxr-xr-x 1 root root 219 Apr 19 13:12 devpi-server
Чтобы добавить путь в переменную PATH нужно в файле .bashrc (расположен в домашней директории пользователя) дописать две строчки:
# nano .bashrc ***** оставляем без изменения а снизу дописываем ***** PATH="/usr/local/bin/:$PATH" export PATH
А затем, чтобы применить изменения, выполните:
# . .bashrc
Теперь вы можете посмотреть версию вашего сервера:
# devpi-server --version 6.5.0
Управлять сервером devpi можно с помощью ещё одного модуля на python, который называется supervisor. Установим его:
# pip3 install supervisor
Теперь вы можете инициализировать devpi, сгенерировать конфиг и запустить сервер!
Инициализируем:
# devpi-init
INFO NOCTX Loading node info from /root/.devpi/server/.nodeinfo
INFO NOCTX generated uuid: 8d832c2dab2248c3ae18a6a088a9befd
INFO NOCTX wrote nodeinfo to: /root/.devpi/server/.nodeinfo
INFO NOCTX DB: Creating schema
INFO [Wtx-1] setting password for user 'root'
INFO [Wtx-1] created user 'root'
INFO [Wtx-1] created root user
INFO [Wtx-1] created root/pypi index
INFO [Wtx-1] fswriter0: commited at 0
# cat /root/.devpi/server/.nodeinfo
{
"role": "standalone",
"uuid": "8d832c2dab2248c3ae18a6a088a9befd"
}
Выше вы видите, что был сгенерирован uuid для нашего индекса, информация об узле была добавлена в файл /root/.devpi/server/.nodeinfo. А также был создан пользователь индекса (root), его пароль (по умолчанию он пустой). И был создан индекс root/pypi.
Теперь сгенерируем конфиги:
# devpi-gen-config --port 4040 --host 0.0.0.0 It is highly recommended to use a configuration file for devpi-server, see --configfile option. wrote gen-config/crontab wrote gen-config/net.devpi.plist wrote gen-config/launchd-macos.txt wrote gen-config/nginx-devpi.conf wrote gen-config/supervisor-devpi.conf wrote gen-config/supervisord.conf wrote gen-config/devpi.service wrote gen-config/windows-service.txt # cat gen-config/supervisord.conf [supervisord] [unix_http_server] file=/tmp/devpi-supervisor.sock [supervisorctl] serverurl=unix:///tmp/devpi-supervisor.sock [rpcinterface:supervisor] supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface [include] files = %(here)s/supervisor-devpi.conf # cat gen-config/supervisor-devpi.conf [program:devpi-server] command=/usr/local/bin/devpi-server --port 4040 --host 0.0.0.0 user = root priority = 999 startsecs = 5 redirect_stderr = True autostart = True
Выше вы видите что создалась директория gen-config с конфигами для devpi сервера. Основной конфиг — это supervisord.conf, и дополнительный supervisor-devpi.conf. В последнем конфиге можно увидеть строку запуска сервера — /usr/local/bin/devpi-server —port 4040 —host 0.0.0.0.
Запустим сервер с помощью supervisord используя основной конфиг:
# supervisord -c gen-config/supervisord.conf
Теперь из server2 проверим что server1 отвечает на веб запросы с помощью curl:
# curl http://192.168.0.62:4040
<!doctype html>
<html>
<head>
<title>devpi</title>
<link rel="stylesheet" type="text/css" href="http://192.168.0.62:4040/+static-4.0.8/style.css" />
<script src="http://192.168.0.62:4040/+static-4.0.8/jquery-1.11.1.min.js"></script>
<script src="http://192.168.0.62:4040/+static-4.0.8/moment-2.8.2.min.js"></script>
<script src="http://192.168.0.62:4040/+static-4.0.8/common.js"></script>
</head>
<body>
*********
</body>
</html>
Также эту страничку можно открыть в браузере:

Сервер у нас пока в состоянии degraded, это потому что он ещё не успел синхронизоваться с Pypi:

После синхронизации статус станет «OK»:

Убедимся что на Server2 нет интернета и пробуем что-нибудь установить с помощью pip используя Server1:
# ping 8.8.8.8
connect: Network is unreachable
# pip3 install -i http://192.168.0.62:4040/root/pypi/+simple/ simplejson --trusted-host 192.168.0.62
Looking in indexes: http://192.168.0.62:4040/root/pypi/+simple/
Collecting simplejson
Downloading http://192.168.0.62:4040/root/pypi/+f/65b/998193bd7b0c7/simplejson-3.17.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (130kB)
100% |████████████████████████████████| 133kB 1.0MB/s
Installing collected packages: simplejson
Successfully installed simplejson-3.17.6
Всё работает!
Управление с помощью supervisord и supervisorctl
Теперь запускать и останавливать ваш devpi сервер, а также посмотреть его статус можно с помощью supervisorctl:
# supervisorctl -c gen-config/supervisord.conf stop devpi-server # supervisorctl -c gen-config/supervisord.conf start devpi-server # supervisorctl -c gen-config/supervisord.conf status devpi-server
Но после перезагрузки сервера вам нужно будет выполнить:
# supervisord -c gen-config/supervisord.conf
А затем управлять службой с помощью supervisorctl.
Управление с помощью systemd
Если ваша система использует systemd, то есть более удобный способ управлять сервером devpi. Для этого нужно скопировать файл службы gen-config/devpi.service в каталог /etc/systemd/system/, и выполнить команду systemctl daemon-reload:
# cp gen-config/devpi.service /etc/systemd/system/ # systemctl daemon-reload
А дальше вы можете запустить службу, добавить её в автозагрузку и управлять с помощью systemctl:
# systemctl start devpi # systemctl enable devpi # systemctl status devpi # systemctl stop devpi
Облегчаем процесс установки пакетов
Как вы помните, нам приходится устанавливать пакеты с помощью такой команды:
# pip3 install -i http://192.168.0.62:4040/root/pypi/+simple/ simplejson --trusted-host 192.168.0.62
То есть нужно указывать адрес индекса и написать --trusted-host так как наш сервер не использует https.
Чтобы pip по умолчанию использовал нужный индекс, нужно создать в домашней директории каталог и конфиг для pip:
# mkdir .pip # nano .pip/pip.conf [global] index-url = http://192.168.0.62:4040/root/pypi/+simple/ [search] index = http://192.168.0.62:4040/root/pypi/
Теперь вы можете не указывать индекс:
# pip3 install getmac --trusted-host 192.168.0.62
Чтобы избавится от —trusted-host нужно на сервер поставить какой-нибудь веб-сервер (apache2 или nginx). Настроить на нём https и использовать в качестве прокси. Кстати, конфиг для nginx уже лежит в каталоге gen-config:
# cat gen-config/nginx-devpi.conf
server {
server_name localhost $hostname "";
listen 80;
gzip on;
gzip_min_length 2000;
gzip_proxied any;
gzip_types application/json;
proxy_read_timeout 60s;
client_max_body_size 64M;
# set to where your devpi-server state is on the filesystem
root /root/.devpi/server;
# try serving static files directly
location ~ /+f/ {
# workaround to pass non-GET/HEAD requests through to the named location below
error_page 418 = @proxy_to_app;
if ($request_method !~ (GET)|(HEAD)) {
return 418;
}
expires max;
try_files /+files$uri @proxy_to_app;
}
# try serving docs directly
location ~ /+doc/ {
# if the --documentation-path option of devpi-web is used,
# then the root must be set accordingly here
root /root/.devpi/server;
try_files $uri @proxy_to_app;
}
location / {
# workaround to pass all requests to / through to the named location below
error_page 418 = @proxy_to_app;
return 418;
}
location @proxy_to_app {
proxy_pass http://localhost:4040;
proxy_set_header X-outside-url $scheme://$http_host;
proxy_set_header X-Real-IP $remote_addr;
}
}
На этом всё, спасибо за внимание!