Skip to content

aws-sam-cliのlocalクライアントからホストのポートにアクセスできない

以下のコマンドを実行した後は、 host.docker.internal で通信が可能になる。

Terminal window
sysctl -w net.ipv4.conf.docker0.route_localnet=1
iptables -t nat -I PREROUTING -i docker0 -d 172.17.0.1 -p all -j DNAT --to 127.0.0.1
iptables -t filter -I INPUT -i docker0 -d 127.0.0.1 -p all -j ACCEPT

いつも実行するのは面倒なので、docker.servicesystemd editを使って一部の設定を更新するのように更新して、毎回実行するように変更してもいいかもしれない。

[Service]
ExecStartPost=/bin/sysctl -w net.ipv4.conf.docker0.route_localnet=1
ExecStartPost=/sbin/iptables -t nat -I ...
ExecStartPost=/sbin/iptables -t filter -I ...

以下は調査した記録。

Linuxで、Lambda関数から localhost:8080 (ポート番号はなんでもいい)にアクセスするコードを sam local start-api で実行するとエラーになる。macOSなら動作するらしい。

ローカル側でネットワーク接続を受け付けるサービスを実行する。pkgsite を使っているが、動いていればなんでもいい。

Terminal window
pkgsite -http=:8080

この状態で、 localhost:8080 にアクセスするLambda関数を実装する。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
MyLambdaFunction:
Type: AWS::Serverless::Function
Properties:
PackageType: Image
Architectures:
- x86_64
Events:
MyAPI:
Type: Api
Properties:
Path: /
Method: GET
Metadata:
DockerContext: .
Dockerfile: Dockerfile
Outputs:
ApiURL:
Description: URL of the API
Value: !GetAtt ApiURL.Arn
FROM golang:1.23 AS build
WORKDIR /go/src
COPY main.go go.mod go.sum ./
RUN go build -o /go/bin/bootstrap
FROM public.ecr.aws/lambda/provided:al2023
COPY --from=build /go/bin/bootstrap /var/runtime/bootstrap
ENTRYPOINT ["/var/runtime/bootstrap"]
package main
import (
"context"
"io"
"net/http"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type Response events.APIGatewayProxyResponse
func HandleRequest(ctx context.Context) (Response, error) {
resp, err := http.Get("http://localhost:8080/")
if err != nil {
return Response{StatusCode: 500, Body: err.Error()}, nil
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return Response{StatusCode: 500, Body: err.Error()}, nil
}
return Response{StatusCode: 200, Body: string(b)}, nil
}
func main() {
lambda.Start(HandleRequest)
}

go.modgo.sum は省略。

以下のコマンドでサーバを立ち上げる。

Terminal window
sam build
sam local start-api

他の端末からアクセスするが、ここではエラーになる。

Terminal window
$ curl http://localhost:3000/
Get "http://localhost:8080/": dial tcp [::1]:8080: connect: connection refused

プロトコルがHTTPSの場合は以下のようなエラーになる。net/http としてはTLSを期待しているところに、サーバから生のHTTPが返却されているためエラーとしているようだが、なぜこうなるのかは分からない。

Terminal window
$ curl http://localhost:3000/
Get "https://localhost:8080/": http: server gave HTTP response to HTTPS client

Dockerは host-gateway という名前の特別なホストを管理していて、これを使ってLinuxでも host.docker.internal を再現できる。

Terminal window
docker run -ti --rm --add-host=host.docker.internal:host-gateway debian:bookworm bash

これを実行すると、コンテナ内の /etc/hosts にエントリが追加される。

172.17.0.1 host.docker.internal

ルーティング的にもコンテナの外に向いている。

Terminal window
root@38acc6ae11d9:/# ip r
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2
root@38acc6ae11d9:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
257: eth0@if258: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

—add-hosts オプションは、 docker compose の場合 extra_hosts プロパティで行う。

---
services:
service1:
extra_hosts:
- host.docker.internal:host-gateway

sam-cli では —add-host オプションがあるので使う。

Terminal window
sam local start-api --add-host=host.docker.internal:host-gateway

コード側で、アクセスするURLを変更する。

resp, err := http.Get("http://localhost:8080/")
resp, err := http.Get("http://host.docker.internal:8080/")

iptables を実行しているなら停止すると解決するかもしれない。Dockerで直接 —add-host して試したときの結果は以下のようになる。

Terminal window
$ docker run -ti --rm --add-host=host.docker.internal:host-gateway debian:bookworm bash
root@ddf3af11eef1:/# apt update
root@ddf3af11eef1:/# apt install curl
root@ddf3af11eef1:/# curl http://host.docker.internal:8080/
curl: (7) Failed to connect to localhost port 8080 after 0 ms: Couldn't connect to server

手元だと iptables を止めたら疎通するようになった。

Terminal window
run0 systemctl stop iptables.service
run0 systemctl restart docker.service

iptables だけ停止すると、sam-clistart-api する度に以下のエラーが出力されるので、なんらかの操作をDockerから iptables へ行っているとは思うが詳細は調べていない。

Unable to enable DNAT rule: (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 127.0.0.1 --dport 8949 -j DNAT --to-destination 172.17.0.2:8080 ! -i docker0: iptables: No chain/target/match by that name.