2018년 10월 11일 목요일

Python을 위한 local private repository 만들기 (bandersnatch + nginx)

Python은 그 특성상 필요에 따라 자주 새로운 package를 설치해서 써야 하는데, 이를 위해서는 해당 서버가 443번 포트를 통해 인터넷에 연결되어 있는 환경이어야 합니다.  그러나 실제로는 대부분의 기업용 data center들에서는 인터넷과의 연결이 완전히 단절되어 있으므로 python을 쓰기에 매우 불편합니다.   이는 x86이든 ppc64le이든 arm이든 모든 CPU 아키텍처에서 공통적으로 골치아파하는 문제이며, 국내 대기업들은 물론 해외 유명 인터넷 업체에서도 다 공통적으로 겪는 문제입니다.  가령 Pillow라는 package를 import해서 쓰려면 먼저 그 package를 설치해야 하는데, pip 명령으로 설치하면 아래처럼 그때그때 인터넷에서 source를 download 받아서 즉석에서 build해서 설치합니다.


[bsyu@p57a22 ~]$ pip install Pillow
Collecting Pillow
  Downloading https://files.pythonhosted.org/packages/1b/e1/1118d60e9946e4e77872b69c58bc2f28448ec02c99a2ce456cd1a272c5fd/Pillow-5.3.0.tar.gz (15.6MB)
    100% |████████████████████████████████| 15.6MB 11.6MB/s
Building wheels for collected packages: Pillow
  Running setup.py bdist_wheel for Pillow ... done
  Stored in directory: /home/bsyu/.cache/pip/wheels/df/81/28/47e761b5e307472ba7c2c5ced6e52037bbefe33c9c4b2a627e
Successfully built Pillow
Installing collected packages: Pillow
Successfully installed Pillow-5.3.0


이런 문제를 해결하는 왕도는 따로 없으나, 가장 근본적인 해결책은 외부망과 분리된 data center 내에 python을 위한 private local repository를 구성하는 것입니다.   다만 여기에는 2가지 문제가 있습니다.

1) Public python repository는 pypi.org (실제로는 files.pythonhosted.org) 인데, 전체 repository 크기가 800GB가 넘고, 날마다 계속 커지고 있습니다.

2) 기존 python package들의 새버전과, 또 아예 새로 만들어지는 python package들이 날마다 쏟아져 나오므로 주기적으로 자주 update를 해줘야 합니다.

보통 1번 문제는 (돈 있는 기업들 입장에서는) 큰 문제가 아닙니다.  Local repository server에 넉넉한 크기의 disk를 쓰면 되기 때문입니다.   문제는 2번 문제인데, 이것도 뾰족한 방법은 없고 외부에서 (1달에 1번 정도) 주기적으로 pypi.org의 내용을 backup 받아서 USB 외장 disk에 담아 data center로 들여온 뒤 local repository server에 부어주는 수 밖에 없습니다.

이런 정책만 정해지면 local repository server를 구성하는 것은 그다지 어렵지 않습니다.    다음과 같이 bandersnatch라는 package를 이용하면 쉽습니다.

아래의 모든 테스트는 ppc64le 아키텍처인 IBM POWER8 processor와 Redhat 7.5를 이용해서 수행되었습니다.  혹자는 x86_64를 위한 python repository는 어디 있는지 알겠는데 ppc64le를 위한 것은 어디 있냐고 여쭈시는 분도 있습니다만,  x86이나 ppc64le나 aarch64나 모두 같은 repository를 사용합니다. 


먼저, 다음과 같이 Anaconda3가 설치된 환경에서 pip로 bandersnatch를 설치합니다. 

[bsyu@p57a22 ~]$ which pip
~/anaconda3/bin/pip

[bsyu@p57a22 ~]$ pip install -r https://bitbucket.org/pypa/bandersnatch/raw/stable/requirements.txt
...
Successfully installed apipkg-1.4 bandersnatch-2.0.0 coverage-4.3.1 execnet-1.4.1 mock-2.0.0 packaging-16.8 pbr-1.10.0 pep8-1.7.0 py-1.4.32 pyflakes-1.3.0 pyparsing-2.1.10 pytest-3.0.5 pytest-cache-1.0 pytest-catchlog-1.2.2 pytest-codecheckers-0.2 pytest-cov-2.4.0 pytest-timeout-1.2.0 python-dateutil-2.6.0 requests-2.12.4 setuptools-33.1.1 six-1.10.0 xmlrpc2-0.3.1

[bsyu@p57a22 ~]$ which bandersnatch
~/anaconda3/bin/bandersnatch

그리고 'bandersnatch mirror' 명령을 수행합니다.   그러면 기본 /etc/bandersnatch.conf을 만들어줍니다.  이 때문에 이 명령은 일단 sudo 권한이 필요합니다.

[bsyu@p57a22 ~]$ sudo /home/bsyu/anaconda3/bin/bandersnatch mirror
2018-10-10 03:31:32,069 WARNING: Config file '/etc/bandersnatch.conf' missing, creating default config.
2018-10-10 03:31:32,069 WARNING: Please review the config file, then run 'bandersnatch' again.

이 /etc/bandersnatch.conf를 필요에 따라 수정합니다.  여기서는 어느 directory에 python repository를 내려받을 것인지만 수정했습니다.

[bsyu@p57a22 ~]$ sudo vi /etc/bandersnatch.conf
...
; directory = /srv/pypi
directory = /home/bsyu/files/pypi
...

다시 'bandersnatch mirror' 명령을 수행하면 정규 public repository (files.pythonhosted.org)를 통째로 download 받습니다.   800GB가 넘는다는 점에 유의하시기 바랍니다.  저도 끝까지 download 받아본 적은 없고, disk가 부족하여 도중에 끊어야 했습니다.

[bsyu@p57a22 ~]$ bandersnatch mirror
2018-10-10 03:33:35,064 INFO: bandersnatch/2.0.0 (cpython 3.7.0-final0, Linux ppc64le)
2018-10-10 03:33:35,064 INFO: Setting up mirror directory: /home/bsyu/files/pypi/
2018-10-10 03:33:35,064 INFO: Setting up mirror directory: /home/bsyu/files/pypi/web/simple
2018-10-10 03:33:35,064 INFO: Setting up mirror directory: /home/bsyu/files/pypi/web/packages
2018-10-10 03:33:35,064 INFO: Setting up mirror directory: /home/bsyu/files/pypi/web/local-stats/days
2018-10-10 03:33:35,064 INFO: Generation file missing. Reinitialising status files.
2018-10-10 03:33:35,065 INFO: Status file missing. Starting over.
2018-10-10 03:33:35,065 INFO: Syncing with https://pypi.python.org.
2018-10-10 03:33:35,065 INFO: Current mirror serial: 0
2018-10-10 03:33:35,065 INFO: Syncing all packages.
2018-10-10 03:33:37,934 INFO: Trying to reach serial: 4358961
2018-10-10 03:33:37,934 INFO: 154638 packages to sync.
...
2018-10-10 03:53:50,360 INFO: Downloading: https://files.pythonhosted.org/packages/8a/5c/625ac1a93da3a672f52d947023770331b958a1100cd9889b727cde5f7ba5/CommonMark-0.7.5-py2.py3-none-any.whl
2018-10-10 03:53:50,360 DEBUG: Getting https://files.pythonhosted.org/packages/8a/5c/625ac1a93da3a672f52d947023770331b958a1100cd9889b727cde5f7ba5/CommonMark-0.7.5-py2.py3-none-any.whl (serial None)
2018-10-10 03:53:50,363 INFO: Syncing package: CompCamps-Cash-Api (serial 4042164)
2018-10-10 03:53:50,363 DEBUG: Getting /pypi/CompCamps-Cash-Api/json (serial 4042164)
2018-10-10 03:53:50,370 INFO: Downloading: https://files.pythonhosted.org/packages/77/16/44a297228a439484d049cdad818c7f6691c162b4cd741c619caeb208bb1e/CommonMark-0.7.5.tar.gz
2018-10-10 03:53:50,371 DEBUG: Getting https://files.pythonhosted.org/packages/77/16/44a297228a439484d049cdad818c7f6691c162b4cd741c619caeb208bb1e/CommonMark-0.7.5.tar.gz (serial None)
...

이제 해당 directory에 가보면 다음과 같이 web/packages 밑에 실제 python package 파일들이 download 되어진 것을 보실 수 있습니다.   web/simple은 그 파일들에 대한 index directory입니다.

[bsyu@p57a22 ~]$ cd files/pypi

[bsyu@p57a22 pypi]$ ls
generation  todo  web

[bsyu@p57a22 pypi]$ cd web

[bsyu@p57a22 web]$ ls
local-stats  packages  simple

[bsyu@p57a22 web]$ cd packages/

[bsyu@p57a22 packages]$ ls
00  0b  16  21  2c  37  42  4e  59  64  6f  7a  85  90  9b  a6  b1  bc  c7  d2  dd  e8  f3  fe
01  0c  17  22  2d  38  43  4f  5a  65  70  7b  86  91  9c  a7  b2  bd  c8  d3  de  e9  f4  ff
02  0d  18  23  2e  39  44  50  5b  66  71  7c  87  92  9d  a8  b3  be  c9  d4  df  ea  f5
03  0e  19  24  2f  3a  45  51  5c  67  72  7d  88  93  9e  a9  b4  bf  ca  d5  e0  eb  f6
04  0f  1a  25  30  3b  46  52  5d  68  73  7e  89  94  9f  aa  b5  c0  cb  d6  e1  ec  f7
05  10  1b  26  31  3c  47  53  5e  69  74  7f  8a  95  a0  ab  b6  c1  cc  d7  e2  ed  f8
06  11  1c  27  32  3d  49  54  5f  6a  75  80  8b  96  a1  ac  b7  c2  cd  d8  e3  ee  f9
07  12  1d  28  33  3e  4a  55  60  6b  76  81  8c  97  a2  ad  b8  c3  ce  d9  e4  ef  fa
08  13  1e  29  34  3f  4b  56  61  6c  77  82  8d  98  a3  ae  b9  c4  cf  da  e5  f0  fb
09  14  1f  2a  35  40  4c  57  62  6d  78  83  8e  99  a4  af  ba  c5  d0  db  e6  f1  fc
0a  15  20  2b  36  41  4d  58  63  6e  79  84  8f  9a  a5  b0  bb  c6  d1  dc  e7  f2  fd


이렇게 download된 generation todo web의 3개 directory를 포함하는 python repository directory 전체 (여기서는 /home/bsyu/files/pypi)를 USB 외장 disk 등에 복사하여 local repository로 사용할 서버에 옮깁니다.   여기서는 편의상 그냥 download 받은 서버에서 직접 local repository 서버를 구성하겠습니다.

https을 통해서 다른 서버들에게 python repository 서비스를 하기 위해서는 당연히 web 서버를 구성해야 합니다.  여기서는 간단하게 nginx를 사용하겠습니다.  먼저 nginx를 YUM 명령으로 설치한 뒤, /etc/nginx/nginx.conf를 수정합니다.  단, 여기서 http(80번 포트)가 아닌 https(443번 포트)를 구성해야 합니다.  그리고 root를 복사한 directory 중 'web' directory로 정해야 하고, SSL certificate과 그 key도 등록해야 합니다.   server_name은 FQDN(Fully Qualified Domain Name)으로 합니다.  여기서는 IP address로 했습니다.

[bsyu@p57a22 ~]$ sudo yum install nginx

[bsyu@p57a22 ~]$ sudo vi /etc/nginx/nginx.conf
...
    server {
        listen       443 ssl http2 default_server;
        listen       [::]:443 ssl http2 default_server;
#        root         /usr/share/nginx/html;
        root         /home/bsyu/files/pypi/web;
        autoindex on;
        charset utf-8;

#        server_name  _;
        server_name  129.40.xx.xx;
        ssl_certificate "/etc/pki/nginx/private/domain.crt";
        ssl_certificate_key "/etc/pki/nginx/private/domain.key";
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
...

이제 SSL 구성을 위해 crt와 key를 생성합니다.  여기서는 1년간 유효한 Self-Signed Certificate (x509)를 만들었습니다.

[bsyu@p57a22 ~]$ cd /etc/pki/nginx/private

[root@p57a22 private]# openssl req -newkey rsa:2048 -nodes -keyout domain.key -x509 -days 365 -out domain.crt
Generating a 2048 bit RSA private key
.......................................................................................+++++
..............................+++++
writing new private key to 'domain.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:.
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:129.40.xx.xx
Email Address []:.

[root@p57a22 private]# ls -ltr
total 8
-rw-r--r-- 1 root root 1704 Oct 11 00:13 domain.key
-rw-r--r-- 1 root root 1107 Oct 11 00:13 domain.crt

생성된 certificate과 key의 검사는 아래와 같이 할 수 있습니다. 

[root@p57a22 private]# openssl x509 -text -noout -in domain.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            a7:03:d9:7b:33:2d:53:7c
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=129.40.xx.xx
        Validity
            Not Before: Oct 11 04:13:00 2018 GMT
            Not After : Oct 11 04:13:00 2019 GMT
        Subject: CN=129.40.116.82
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:ca:73:3c:7f:e6:29:1e:5e:7b:ff:b5:98:30:ce:
                    fb:48:0e:bc:96:fd:5b:7f:1e:23:e5:62:8f:74:8e:
 ...

[root@p57a22 private]# openssl rsa -check -in domain.key
RSA key ok
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAynM8f+YpHl57/7WYMM77SA68lv1bfx4j5WKPdI6ftW2lRdrw
fUikVx0C+2ni3QB6y/xuT8yT0eiCPT5Ak4yGpDSsfcfzDOFgVSB02irWmX/KUNIS
/zS+E7SkAfariUEFa8iRjt2kmDpi65YGKH9NY7p136NcZOSZQx2wsAU0UM5Pjtci
....

이제 생성된 certificate과 key를 합해 PEM을 만듭니다. 

[root@p57a22 private]# cat domain.crt domain.key >> domain.pem

이제 nginx를 start 합니다.

[root@p57a22 private]# systemctl status nginx

그리고 pip에서 이 129.40.xx.xx를 trusted-host로 사용하도록 ~/.pip/pip.conf를 생성하여 다음과 같은 내용을 넣어줍니다.

[bsyu@p57a22 ~]$ vi .pip/pip.conf
[global]
trusted-host = 129.40.xx.xx
index = https://129.40.xx.xx:443/packages
index-url = https://129.40.xx.xx:443/simple
cert = /etc/pki/nginx/private/domain.pem

이제 CommonMark라는 package를 설치해봅니다.   기존처럼 files.pythonhosted.org가 아니라 129.40.xx.xx에서 package source를 가져오는 것을 보실 수 있습니다.

[bsyu@p57a22 ~]$ pip install CommonMark
Looking in indexes: https://129.40.xx.xx:443/simple
Collecting CommonMark
  Downloading https://129.40.xx.xx:443/packages/ab/ca/439c88039583a29564a0043186875258e9a4f041fb5c422cd387b8e10175/commonmark-0.8.1-py2.py3-none-any.whl (47kB)
    100% |████████████████████████████████| 51kB 37.8MB/s
Requirement already satisfied: future in ./anaconda3/lib/python3.7/site-packages (from CommonMark) (0.16.0)
Installing collected packages: CommonMark
Successfully installed CommonMark-0.8.1

댓글 1개:

  1. Hw 엔지니어를 위한 Deep Learning: Python을 위한 Local Private Repository 만들기 (Bandersnatch + Nginx) >>>>> Download Now

    >>>>> Download Full

    Hw 엔지니어를 위한 Deep Learning: Python을 위한 Local Private Repository 만들기 (Bandersnatch + Nginx) >>>>> Download LINK

    >>>>> Download Now

    Hw 엔지니어를 위한 Deep Learning: Python을 위한 Local Private Repository 만들기 (Bandersnatch + Nginx) >>>>> Download Full

    >>>>> Download LINK

    답글삭제