はじめに
lambdaからEC2内のコンテナにSSH、FTP接続したときのメモです。lambdaでpythonのparamikoをインポートしようとすると下記のエラーが出て躓いてましたが、chatGPTの力を借りて解決できました。
libc.musl-x86_64.so.1: cannot open shared object file: No such file or directory
目的
- 同じVPC内にあるlambdaからEC2内のコンテナにSSH,FTP接続する。
- pythonのparamikoライブラリを使ってSSH接続する。
- pythonのftplibライブラリを使ってFTP接続する。
環境
-
EC2
- Red Hat Enterprise Linux
- t2.micro
-
lambda
- Python 3.9
- x86_64
EC2
セキュリティグループ
一旦全てのアクセス、全てのポートからアクセス可能に設定しておく。
コンテナ作成
podman network作成
下記のコマンドを実行し、test_networkという名前のpodman networkを作成する。
podman network create test_network
serverコンテナ作成
pure-ftpdをベースに下記のDockerfileから作成する。
# ベースイメージとしてftpdを使用
FROM docker.io/stilliard/pure-ftpd
# arg
ARG PUBLICHOST
ARG USER
ARG PASSWORD
#env
ENV PUBLICHOST=$PUBLICHOST
ENV FTP_USER_NAME=$USER
ENV FTP_USER_PASS=$PASSWORD
ENV FTP_USER_HOME="/home/$USER"
# 必要なパッケージをインストール
RUN apt-get update && \
apt-get install -y openssh-server && \
mkdir /var/run/sshd
# パスワードのみでのSSH接続を許可する
RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config
# RUN sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config
# testuserを作成
RUN useradd -m -s /bin/bash "$FTP_USER_NAME" && \
echo "$FTP_USER_NAME:$FTP_USER_PASS" | chpasswd && \
chmod 777 -R "/home/$FTP_USER_NAME"
# SSHサービスを起動するスクリプトを作成
RUN echo '#!/bin/bash\nservice ssh start\nexec "$@"' > /start.sh && chmod +x /start.sh
# SSHとPure-FTPdの両方を起動
CMD ["sh", "-c", "/start.sh && /run.sh -l puredb:/etc/pure-ftpd/pureftpd.pdb -E -j -R -P $PUBLICHOST"]
下記コマンドでコンテナイメージをビルドする。イメージの名前はserverとした。
podman build \
-t server \
--build-arg PUBLICHOST=localhost \
--build-arg USER=testuser \
--build-arg PASSWORD=password \
.
下記コマンドでコンテナを起動する。コンテナの名前はserverとした。
SSH用のポート22をホストの2022に、FTP用のポート21をホストの2021にそれぞれマウントする。また、コンテナのipアドレスを10.89.0.6に固定した。
podman run -d --tty \
--name server \
--network test_network \
-p 2021:21 \
-p 2022:22 \
-p 30000-30009:30000-30009 \
--ip 10.89.0.6 \
--privileged \
server
clientコンテナ作成(任意)
SSH、FTPをEC2内で確認するためにclientコンテナを作成する。
下記のDockerfileから作成する。
FROM python:3.9-alpine
RUN apk update
RUN apk --update-cache add \
python3 \
python3-dev \
py3-pip \
gcc \
g++ \
curl \
bash \
openssh-client
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
下記コマンドでコンテナイメージをビルドする。イメージの名前はclientとした。
podman build -t client .
下記コマンドでコンテナを起動する。コンテナの名前はclientとした。
また、ipアドレスを10.89.0.5に固定した。
podman run -d --tty \
--name client \
--network test_network \
--ip 10.89.0.5 \
client
lambda
アクセス権限
IAMロールを修正する。
下記2つポリシーを追加する。
- AmazonEC2ReadOnlyAccess
- AWSLambdaVPCAccessExecutionRole
VPC、セキュリティグループ
一旦全てのアクセス、全てのポートからアクセス可能に設定しておく。
VPCはEC2と同じものを設定する。
zipファイル作成
Dockerを使ってzipファイルを作成する。
AWSのlambdaと同じ環境でビルドしないと下記のエラーが出てpythonでparamikoがインポートできなかった。
libc.musl-x86_64.so.1: cannot open shared object file: No such file or directory
AWSのlambda python3.9のイメージ(public.ecr.aws/lambda/python:3.9)が公開されているので、これを使用する。
下記のDockerfileを使用して作成する。ここではpythonのライブラリparamiko, scp, pysftpをインストールしているが、SSH接続するだけならparamikoだけでOK。
FROM public.ecr.aws/lambda/python:3.9
RUN yum install -y gcc libffi-devel python3-devel
RUN mkdir -p /workspace/python-pkg /workspace/python-pkg/pkg
# 必要なファイルをコピー
COPY lambda_function.py /workspace
# pythonのライブラリをインストール
WORKDIR /workspace/python-pkg
RUN pip install paramiko scp pysftp -t .
# pythonライブラリとlambda_function.pyをzipにまとめる
WORKDIR /workspace
RUN zip -r lambda-pkg.zip .
# ENTRYPOINTを上書きする
ENTRYPOINT ["/bin/bash", "-l", "-c", "cp /workspace/lambda-pkg.zip /workspace/pkg/lambda-pkg.zip"]
下記コマンドでコンテナイメージをビルドする。イメージの名前はclientとした。
ビルドする前にローカル環境にlambda_function.pyを作成しておく。
podman build -t make-lambda-pkg .
下記コマンドでコンテナを作成する。
コンテナにマウントするためのpkgディレクトリを作成しておく。
コンテナ起動時にlambdaにアップロードするためのlambda-pkg.zipファイルがマウントされたpkgディレクトリに作成される。
podman run --rm --tty \
-v $(pwd)/pkg:/workspace/pkg:Z \
make-lambda-pkg
関数作成
zipファイルをアップロードする。ファイルサイズが10M以上の場合はS3からアップロードする。
lambda_function.pyを下記のように作成する。
import boto3
from ftplib import FTP
import json
import os
import sys
sys.path.append('python-pkg')
import paramiko
import scp
import pysftp
CUR_DIR = os.getcwd()
def test_ssh():
# ログイン情報設定
host = '172.31.15.114' # EC2 private ip
user = 'testuser'
password = 'password'
port=2022
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 接続
try:
ssh.connect(hostname=host, username=user, password=password, port=port)
# コマンド実行例
stdin, stdout, stderr = ssh.exec_command("echo 'Hello from container'")
output = stdout.read().decode()
error = stderr.read().decode()
ssh.close()
# 結果をログに記録
print(f"Command Output: {output}")
print(f"Command Error: {error}")
return {"statusCode": 200, "body": output}
except Exception as e:
print(f"SSH connection failed: {e}")
return {"statusCode": 500, "body": str(e)}
finally:
ssh.close()
def test_ftp():
# ログイン情報
host = '172.31.15.114' # EC2 private ip
user = 'testuser'
password = 'password'
port = 2021
ftp = FTP()
# 接続
try:
ftp.connect(host, port)
ftp.login(user, password)
# ホームディレクトリのファイルリストを取得
ret = ftp.retrlines('LIST')
return {"result": ret}
except Exception as e:
print(f"FTP connection failed: {e}")
return {"result": f"failed {e}"}
finally:
ftp.close()
def lambda_handler(event, context):
# ret = test_ftp()
ret = test_ssh()
return str(ret)
lambdaからEC2内のserverコンテナへのSSH接続
lambda_handler関数のtest_ftp部分をコメントアウトしてテストを実行する。テストイベントはなんでもOK。
def lambda_handler(event, context):
# ret = test_ftp()
ret = test_ssh()
return str(ret)
成功していれば、下記のような出力が得られる。
Status: Succeeded
Test Event Name: test_event
Response:
"{'statusCode': 200, 'body': 'Hello from container\\n'}"
lambdaからEC2内のserverコンテナへのFTP接続
lambda_handler関数のtest_ssh部分をコメントアウトしてテストを実行する。テストイベントはなんでもOK。
def lambda_handler(event, context):
ret = test_ftp()
# ret = test_ssh()
return str(ret)
成功していれば、下記のような出力が得られる。
Status: Succeeded
Test Event Name: test_event
Response:
"{'result': '226-Options: -l \\n226 0 matches total'}"
clientコンテナ、EC2ホスト、PCからコンテナへのSSH接続
おまけ。
各クライアントからEC2のserverコンテナへSSH接続する方法。
指定するホストやポートが異なるので注意。
ユーザにはserverコンテナのユーザを指定する。
clientコンテナから
ホストにコンテナのIPアドレスを指定。ポートはデフォルトの22。
ssh testuser@10.89.0.6 -p 22
EC2ホストから
ホストにlocalhostを指定。 ポートはコンテナにマウントした2022。
ssh testuser@localhost -p 2022
PCから
ホストにEC2のパブリックIPアドレスを指定。 ポートはコンテナにマウントした2022。EC2のpemファイルも必要。
ssh -i xxxxx.pem testuser@3.22.205.47 -p 2022
おわりに
AWSに慣れていないこともあり、時間がかかってしまいました。lambdaでpythonのライブラリを使用するときは、public.ecr.aws/lambda/python:3.9イメージを使ってビルドするのが良さそうです。