はじめに
yumでインストールしたファイルが、何らかの理由で欠損してしまった場合、バージョンの互換性が取れなくなってしまう可能性があるという問題があります。
互換性があるバージョンのファイルをキャッシュレポジトリに保存しておくことで、クライアントは以前使っていたファイルを簡単に入手することができます。
今回はディスクにファイルを保存することができるようなリバースプロキシをflaskで開発しました。
またdockerを用いることで、localhostだけでなく、外からのアクセスにも対応しています。
目次
実行環境
使用したライブラリ
lib | version |
---|---|
pip | 22.2.2 |
Flask | 2.1.3 |
Python | 3.9.12 |
flaskは以下のコマンドでインストールできます。
pip install flask
1. flaskを用いたリバースプロキシの開発
今回開発するリバースプロキシは、クライアントがダウンロードしたいファイルが、リバースプロキシのディスク上にある場合は、Webサーバーに行かずにディスクのファイルを返します。
ディスク上に無い場合は、Webサーバーに取りに行って、ディスクに保存してからクライアントにファイルを返します。
以下実装部分です。
# coding: utf-8
import http.client
import os
from flask import Flask,make_response
from werkzeug.datastructures import Headers
app = Flask(__name__)
@app.route('/repos/<fqdn>/<path:path>')
def change_get_url(fqdn,path):
h = http.client.HTTPConnection(fqdn)
h.request('GET', f'/{path}')
r = h.getresponse()
out_filepath = "var/repos/{}/{}".format(fqdn,path)
paths = path.split('/')
del paths[-1]
path = "/".join(paths)
dir_path = "var/repos/{}/{}".format(fqdn, path)
# ファイルが存在する場合の読み出し
if os.path.exists(out_filepath):
with open(out_filepath,mode='rb') as f:
body = f.read()
f.close()
# ファイルが存在しない場合の書き出し
else:
# r.read()の内容をファイルに書き出して保存する
body = r.read()
# ディレクトリの作成
os.makedirs(dir_path,exist_ok=True)
# r.read()の内容をバイナリファイルに書き出して保存する
with open(out_filepath,mode='wb') as f:
f.write(body)
f.close()
resp = make_response(body)
d = Headers()
d.add('Server',r.headers['Server'])
d.add('Date',r.headers['Date'])
d.add('Content-Type',r.headers['Content-Type'])
d.add('Content-Length',r.headers['Content-Length'])
d.add('Last-Modified',r.headers['Last-Modified'])
d.add('Connection',r.headers['Connection'])
d.add('Etag',r.headers['Etag'])
d.add('Accept-Ranges',r.headers['Accept-Ranges'])
resp.headers = d
resp.status = r.status
return resp
if __name__ == '__main__':
app.run(debug=False,host='0.0.0.0',port=80)
2. リバースプロキシを介したrpmファイルのインストール
先ほどのdnf.cache.pyを用いて、rpmファイルをインストールしていきます。
dnf.cache.pyを実行して、サーバーを172.20.10.3:80で立ち上げた状態で、curlコマンドを実行します。
curl -O -v http://172.20.10.3:80/repos/repo.almalinux.org/vault/8/AppStream/debug/x86_64/Packages/389-ds-base-debuginfo-1.4.3.28-6.module_el8.6.0%2B2734%2B1efaf02b.x86_64.rpm
dnf_cacheにrpmファイルを保存することができていることが分かります。
リバースプロキシを介さずに、rpmファイルをダウンロードした場合とファイルが異なっていないか確かめてみます。
先ほどダウンロードしたrpmファイルに.backをつけます。
mv 389-ds-base-debuginfo-1.4.3.28-6.module_el8.6.0%2B2734%2B1efaf02b.x86_64.rpm 389-ds-base-debuginfo-1.4.3.28-6.module_el8.6.0%2B2734%2B1efaf02b.x86_64.rpm.back
次に本物のWebサーバーからrpmファイルをダウンロードします。
curl -O -v http://repo.almalinux.org/vault/8/AppStream/debug/x86_64/Packages/389-ds-base-debuginfo-1.4.3.28-6.module_el8.6.0%2B2734%2B1efaf02b.x86_64.rpm
cmpコマンドでファイルが一致しているか比較してみます。
cmp 389-ds-base-debuginfo-1.4.3.28-6.module_el8.6.0%2B2734%2B1efaf02b.x86_64.rpm 389-ds-base-debuginfo-1.4.3.28-6.module_el8.6.0%2B2734%2B1efaf02b.x86_64.rpm.back
何も表示されなければ、リバースプロキシを介してダウンロードしたものと、本物のWebサーバーからダウンロードしたrpmファイルが一致しているということになります。
3. Docker上での実行
それでは、localhostだけでなく外からのアクセスに対応するために、Docker上で先ほどのプログラムを動かせるようにしていきます。
まずはDockerイメージを作るためにDockerfileを作成します。
FROM python:3.9-alpine
COPY dnf_cache.py dnf_cache.py
RUN pip install --upgrade pip && pip install flask
ENTRYPOINT ["/usr/local/bin/python", "dnf_cache.py"]
先ほどのDockerfileで、dockerイメージを作成します。
docker build -t dnf_cache .
コンテナ間通信のために、docker-networkを作成します。
docker network create dnf_cache_net
dnf_cacheという名前で、コンテナを起動します。
networkは先ほど作成した、dnf_cache_netとしています。
docker run -d --name dnf_cache --network dnf_cache_net dnf_cache
almalinuxコンテナを、dnf_cache_netで立ち上げます。
docker run -it --network dnf_cache_net --rm almalinux /bin/bash
dnf_cache_net上でコンテナ間通信ができているかは、以下で確認できます。
docker network inspect dnf_cache_net
dnf_cache_netに二つのコンテナが含まれていることが分かります。
[
{
"Name": "dnf_cache_net",
"Id": "a7ba7dd44340f4553fbeddc9d4e8c121b2f448033c84dac8c341d71abb5b7eb3",
"Created": "2022-11-05T06:38:57.619638273Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.21.0.0/16",
"Gateway": "172.21.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"5c32fd260145c37a1e9da61e7efb6e723f853560d7653d40f68f48e5564fdd9b": {
"Name": "dnf_cache",
"EndpointID": "e741a69003cad4aacbb4cbbcd753d1bb6aacd3b422e8380e5a288f4d469ba506",
"MacAddress": "02:42:ac:15:00:02",
"IPv4Address": "172.21.0.2/16",
"IPv6Address": ""
},
"f02458eb74e091ddf9ace7f771133ba352500b4a2ced3466d69e2e6497bf9e30": {
"Name": "dreamy_stonebraker",
"EndpointID": "8aafa7e554adb97c67c48ce93bf72e2edc92ef02d6fd0acfc861ec7096e11b8f",
"MacAddress": "02:42:ac:15:00:03",
"IPv4Address": "172.21.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
almalinuxコンテナ上で、以下を実行します。
sed -i -e 's/^mirrorlist=https:\/\//# mirrorlist=https:\/\//' -e 's/^# baseurl=https:\/\//baseurl=http:\/\/dnf_cache\/repos\//' /etc/yum.repos.d/*.repo
dnf install python39
以下のように表示されれば、コンテナ上でリバースプロキシを介して、pythonをインストールすることができています。
python39 --version
Python 3.9.12
4. 環境変数の設定
最後にクライアントがリバースプロキシを使いたくない場合も想定して、リバースプロキシを使うか否かを環境変数で設定できるようにします。
# coding: utf-8
import http.client
import os
from flask import Flask,make_response
from werkzeug.datastructures import Headers
app = Flask(__name__)
@app.route('/repos/<fqdn>/<path:path>')
def change_get_url(fqdn,path):
h = http.client.HTTPConnection(fqdn)
h.request('GET', f'/{path}')
r = h.getresponse()
out_filepath = "var/repos/{}/{}".format(fqdn,path)
paths = path.split('/')
del paths[-1]
path = "/".join(paths)
dir_path = "var/repos/{}/{}".format(fqdn, path)
if os.environ['CACHE_ONLY'] == 'true':
# ファイルが存在する場合の読み出し
if os.path.exists(out_filepath):
with open(out_filepath,mode='rb') as f:
body = f.read()
f.close()
# ファイルが存在しない場合の書き出し
else:
# r.read()の内容をファイルに書き出して保存する
body = r.read()
# ディレクトリの作成
os.makedirs(dir_path,exist_ok=True)
# r.read()の内容をバイナリファイルに書き出して保存する
with open(out_filepath,mode='wb') as f:
f.write(body)
f.close()
elif os.environ['CACHE_ONLY'] == 'false':
# r.read()の内容をファイルに書き出して保存する
body = r.read()
# ディレクトリの作成
os.makedirs(dir_path, exist_ok=True)
# r.read()の内容をバイナリファイルに書き出して保存する
with open(out_filepath, mode='wb') as f:
f.write(body)
f.close()
else:
print("Set environment variables.")
return
resp = make_response(body)
d = Headers()
d.add('Server',r.headers['Server'])
d.add('Date',r.headers['Date'])
d.add('Content-Type',r.headers['Content-Type'])
d.add('Content-Length',r.headers['Content-Length'])
d.add('Last-Modified',r.headers['Last-Modified'])
d.add('Connection',r.headers['Connection'])
d.add('Etag',r.headers['Etag'])
d.add('Accept-Ranges',r.headers['Accept-Ranges'])
resp.headers = d
resp.status = r.status
return resp
if __name__ == '__main__':
app.run(debug=False,host='0.0.0.0',port=80)
以下のコマンドで、dnf_cacheコンテナを立ち上げるときに、リバースプロキシを使用するか否かを指定することができるようになりました。
docker run -e CACHE_ONLY=true -d --name dnf_cache --network dnf_cache_net dnf_cache
先ほどと同様に実行すれば、リバースプロキシを介してpythonがインストールできることが確認できると思います。