
相信各位使用Python的朋友一定非常熟悉pypi, Python官方的软件仓库, 基本上你机器上安装的Python软件包(例如通过pip install)都是经由官方的pypi registry下载而来。
然而, 发布在pypi上的package都是公开的, 对于有一定规模的公司, 大部分都有自己内部package发布的需求, 这时候发布到pypi上, 就显得非常不合适了。
慢
再者, 官方的pypi在国内访问速度并不理想, 而且由于不可描述的原因还经常不能访问, 于是乎大家通常都可以通过国内大厂友情赞助的mirror进行安装, 虽然对于新package有一定的延时, 不过基本问题不大。
1# 阿里云镜像
2pip install -i https://mirrors.aliyun.com/pypi/simple/
3# 豆瓣镜像
4pip install -i https://pypi.doubanio.com/simple/
对于日常自用, 掌握上面两个地址已经足够。
但是在脉策, 每天有上百个CI在自动化运行, 为了保障一致性, 大部分都需要重新安装或者对比package以减少不同环境切换的风险. 即使使用国内的镜像, 在速度和可靠性上依然满足不了我们的需求。
快
为了满足开发的需求, 我们基于devpi搭建了内网的pypi mirror。
首先要了解一下devpi, 除了提供远程pypi的mirror外, 还提供自发布package的管理, 非常适合公司内部发布package管理, 关于devpi的详细使用, 可以参考官方文档。
接着我们先新建一个文件夹, 准备对应的Dockerfile和docker-entrypoint.sh。
👉Dockerfile
1# 这里参考了 https://github.com/muccg/docker-devpi
2FROM python:3.6.8
3
4# 建立运行devpi需要的devpi user
5RUN useradd -m -U -s /bin/bash devpi
6# 安装devpi对应的package, 可以按需更新对应的version
7RUN pip install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/ \
8 "cffi==1.11.5" \
9 "devpi-client==4.0.3" \
10 "devpi-web==3.3.0" \
11 "devpi-server==4.6.0"
12# 暴露官方端口
13EXPOSE 3141
14# 指定package存储的位置, 可随意
15VOLUME /data
16# 载入对应的entrypoint文件, 用于初始化devpi
17COPY docker-entrypoint.sh /docker-entrypoint.sh
18RUN chmod +x /docker-entrypoint.sh
19
20ENV HOME /data
21WORKDIR /data
22
23ENTRYPOINT ["/docker-entrypoint.sh"]
24CMD ["devpi"]
👉docker-entrypoint.sh
1#!/bin/bash
2
3# 设置devpi的存储路径
4function defaults {
5 : ${DEVPI_SERVERDIR="/data/devpi/server"}
6 : ${DEVPI_CLIENTDIR="/data/devpi/client"}
7
8 echo "DEVPI_SERVERDIR is ${DEVPI_SERVERDIR}"
9 echo "DEVPI_CLIENTDIR is ${DEVPI_CLIENTDIR}"
10
11 export DEVPI_SERVERDIR DEVPI_CLIENTDIR
12}
13
14# 初始化devpi
15function initialise_devpi {
16 echo "[RUN]: Initialise devpi-server"
17 devpi-server --init
18 devpi-server --start --host 127.0.0.1 --port 3141 --serverdir ${DEVPI_SERVERDIR}
19 devpi-server --status
20 devpi use http://localhost:3141
21 # 注意修改成你需要的账户密码
22 devpi user -c ${your_username} email=${your_email} password=${your_password}
23 devpi login ${your_username} --password ${your_password}
24 devpi index -c douban type=mirror mirror_url=http://pypi.douban.com/simple
25 devpi index -c aliyun type=mirror mirror_url=http://mirrors.aliyun.com/pypi/simple/
26 devpi index -c ${your_index} bases=${your_username}/aliyun,${your_username}/douban,root/pypi mirror_whitelist='*'
27 devpi index -y -c public mirror_whitelist='*'
28 devpi-server --stop
29 devpi-server --status
30}
31
32defaults
33
34if [ "$1" = 'devpi' ]; then
35 if [ ! -f $DEVPI_SERVERDIR/.serverversion ]; then
36 initialise_devpi
37 fi
38
39 echo "[RUN]: Launching devpi-server"
40 exec devpi-server --host 0.0.0.0 --port 3141
41fi
42
43echo "[RUN]: Builtin command not provided [devpi]"
44echo "[RUN]: $@"
45
46exec "$@"
假设${your_username}=mdt, ${your_index}=pypi, 通过以上配置, 我们建立了一个名为mdt/pypi的index。
当安装package的时候, 默认会查找本地, 找不到的时候会一次去aliyun, douban, pypi查找。
接着我们需要build对应的image
1docker build -t devpi:4.6.0 .
短暂的等待镜像完成后, 运行
1docker run -d --name pypi -p 3141:3141 -v ${your_directory}:/data devpi:4.6.0
然后就可以通过运行以下命令安装python的package了(假设你的服务器是192.168.1.101)。
1pip install --trusted-host 192.168.1.101 -i http://192.168.1.101:3141/mdt/pypi/
以上基本完成了本地pypi的搭建, 为了有更好的静态文件处理以及https支持, 建议在devpi前面再外接一个nginx。
首先得准备好对应的https的签名文件, 没有的话可以去买一个或者去let's encrypt 免费申请一个, 假设${your_directory}=/mnt/data, 然后运行
1docker exec pypi devpi-server --port 3141 --serverdir /data/devpi/server --gen-config
2cat /mnt/data/gen-config/nginx-devpi.conf
就可以获得对应的nginx配置模板, 稍加修改
1server {
2 # server_name localhost $hostname "";
3 # listen 80;
4
5 # 启用https
6 server_name pypi.abc.com;
7 listen 443 ssl;
8 ssl_certificate abc.com.pem;
9 ssl_certificate_key abc.com.key;
10 ssl_session_timeout 5m;
11 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
12 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
13 ssl_prefer_server_ciphers on;
14
15 gzip on;
16 gzip_min_length 2000;
17 gzip_proxied any;
18 gzip_types text/html application/json;
19
20 proxy_read_timeout 60s;
21 client_max_body_size 64M;
22
23 # set to where your devpi-server state is on the filesystem
24 root /data/devpi/server;
25
26 # try serving static files directly
27 location ~ /\+f/ {
28 # workaround to pass non-GET/HEAD requests through to the named location below
29 error_page 418 = @proxy_to_app;
30 if ($request_method !~ (GET)|(HEAD)) {
31 return 418;
32 }
33
34 expires max;
35 try_files /+files$uri @proxy_to_app;
36 }
37 # try serving docs directly
38 location ~ /\+doc/ {
39 try_files $uri @proxy_to_app;
40 }
41 location / {
42 # workaround to pass all requests to / through to the named location below
43 error_page 418 = @proxy_to_app;
44 return 418;
45 }
46 location @proxy_to_app {
47 # proxy_pass http://localhost:3141;
48 # 指向对应的docker位置
49 proxy_pass http://devpi:3141;
50 proxy_set_header X-outside-url $scheme://$host:$server_port;
51 proxy_set_header X-Real-IP $remote_addr;
52 }
53}
然后我们把devpi和nginx配置到对应的compose.yml
1services:
2 nginx_dev:
3 image: nginx:1.13.9-alpine
4 entrypoint: nginx
5 command: -g "daemon off;" -c /etc/nginx/nginx.conf
6 volumes:
7 - devpi.conf:/etc/nginx/conf.d/devpi.conf:ro
8 - /mnt/data/devpi/server/:/data/devpi/server/:ro
9 ports:
10 # 80, 443
11 - "80:80"
12 - "443:443"
13 mem_limit: 512m
14 networks:
15 - pypi
16
17 devpi:
18 image: devpi:4.6.0
19 volumes:
20 - /mnt/data:/data
21 ports:
22 - 3141:3141
23 mem_limit: 1g
24 networks:
25 - pypi
26
27networks:
28 pypi:
29 driver: bridge
然后运行
1docker-compose up -d
就可以通过https方式访问了
1pip install -i https://pypi.abc.com/mdt/pypi/
内网发布
正如上面所提, 除了能够提供一流的本地下载体验外, devpi也提供了内部package的发布功能, 要使用这个功能, 需要几项条件:
本身项目需要按要求准备好对应的setup.py, 具体配置方式可以参考python packaging
安装有devpi的python环境
需要发布的devpi index
对应index的账号密码
假设我们要发布到 https://pypi.abc.com/mdt/pypi/
1# 进入对应的python虚拟环境, 这里用的virtualenvwrapper
2workon devpi
3# 切换到对应的index
4devpi use https://pypi.abc.com/mdt/pypi/
5# 登录
6devpi login mdt --password ${your_password}
7devpi upload --index mdt/pypi
然后就可以像平时安装package一样去安装了。

