Lambda上でwgetコマンドを実行できるか?
エンジニアの内山です。今回は、Lambda上でwgetコマンドを入れるときに色々と手間取ったので、その知見を共有させていただきます。
Lambdaにはwgetコマンドがない
Lambdaには、デフォルトでwgetコマンドはインストールされていません。そのため、Lambda上にwgetバイナリファイルをアップロードする必要があります。
しかし、どこかのサーバにインストールされているwgetバイナリファイルを、単純にアップロードして実行すると、以下のようなエラーが出てしまいます。
/var/task/handlers/bin/wget:
error while loading shared libraries:
libgnutls.so.28: cannot open shared object file:
No such file or directory
このエラーは、wgetが依存しているShared Objectが存在しないために起こります。そのため、関連するShared ObjectもLambdaにアップロードする必要があります。
今回は、以下のような手順で、wgetコマンドをLambda上で、実行できるようにしました。
- Dockerコンテナ上でwgetをソースからビルド
- Dockerコンテナ上からwgetバイナリファイルをコピーして、Lambdaにアップロード
- Dockerコンテナ上からwgetが依存しているShared Objectをコピーして、Lambda Layerにアップロード
- Lambda関数にLambda Layerを関連付ける
以下、それぞれの詳細を紹介していきます。
Dockerコンテナ上でwgetをソースからビルド
以下のDockerfileを作り、wgetをDockerコンテナ上でビルドします。
FROM lambci/lambda:build-python3.8
ENV VERSION wget-1.18
RUN yum install -y tar gzip gcc make gnutls-devel pkgconfig
RUN curl -o ${VERSION}.tar.gz ftp.gnu.org/pub/gnu/wget/${VERSION}.tar.gz &&
tar xzvf ${VERSION}.tar.gz &&
cd ${VERSION} &&
./configure --with-gnu-ld --disable-pcre --disable-threads --disable-ipv6 --disable-ntlm &&
make &&
mv src/wget /
今回はPythonからwgetコマンドを実行する形にしているため、Dockerイメージはlambda:build-pythonを使用しています。
wgetをソースからビルドしているのは、不要な機能はオフにして、依存するShared Objectを最小限にするためです。
ビルドは、以下のように実行します。
$ docker build -t wget:1 .
Dockerコンテナ上からwgetバイナリファイルをコピーして、Lambdaにアップロード
ビルドが終了したら、以下のように、Lambdaに上げる用のディレクトリを作成し、ホスト側にwgetバイナリファイルをコピーしてきます。
$ mkdir handlers/bin
$ docker cp $(docker run -d wget:1 bash):/wget handlers/bin
また、wgetコマンドを実行するPythonスクリプトを以下のように実装しました。
import subprocess
import tempfile
import os
basedir=os.path.dirname(__file__)
def lambda_handler(event, context):
with tempfile.TemporaryDirectory() as dir_name:
wget(dir_name, 'https://example.com/')
def wget(dir_name, url: str) -> str:
cmd = '{}/bin/wget -k -r 1 -P {} {}'.format(basedir, dir_name, url)
result = subprocess.run(
cmd.split(" "),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
print(result.stdout.decode())
return dir_name
$ zip -r main.zip handlers
main.zipは、Lambdaにアップロードします。
Dockerコンテナ上からwgetが依存しているShared Objectをコピーして、Lambda Layerにアップロード
今回は以下のShared Objectが必要でした。
- /lib64/libgnutls.so.28
- /lib64/libnettle.so.4
- /lib64/libhogweed.so.2
- /lib64/libgmp.so.10
これらすべてがシンボリックリンクなので注意してください。
Lambda Layer用のディレクトリを作成し、必要なShared ObjectをDockerコンテナからコピーしてきます。
$ mkdir lib
$ docker cp $(docker run -d wget:1 bash):/lib64/libgnutls.so.28.43.3 lib/libgnutls.so.28
$ docker cp $(docker run -d wget:1 bash):/lib64/libnettle.so.4.7 lib/libnettle.so.4
$ docker cp $(docker run -d wget:1 bash):/lib64/libhogweed.so.2.5 lib/libhogweed.so.2
$ docker cp $(docker run -d wget:1 bash):/lib64/libgmp.so.10.2.0 lib/libgmp.so.10
Lambdaの実行環境によっては、必要になってくるShared Objectが違うかもしれないので、適宜必要なものをコピーしてください。
コピーしたものは、zipで固めます。
$ zip layer.zip lib/*
layer.zipをレイヤーにアップロードします。今回は、Python3.8ランタイムで実行する想定です。
Lambda関数にLambda Layerを関連付ける
以下の画面で、レイヤーをLambda関数に関連付けます。
Lambdaを実行して、wgetコマンドが実行できていることを確認
Lambdaを試しに実行すると、以下のようなメッセージが出力され、wgetコマンドを実行できていることを確認できると思います。
--2020-08-07 04:56:56-- https://example.com/
Resolving example.com... 93.184.216.34
Connecting to example.com|93.184.216.34|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1256 (1.2K) [text/html]
Saving to: ‘/tmp/tmpnpvavroa/example.com/index.html’
0K . 100% 59.1M=0s
おわりに
色々と手間取りましたが、Lambda上でwgetコマンドを実行できるようになりました。
参考になれば幸いです。