CI: Postgres on 127.0.0.1, because the runner uses host networking
CI / verify (push) Successful in 1m59s
CI / verify (push) Successful in 1m59s
The runner is configured with `container.network: host`. That one setting
explains all three earlier failures, and the workflow now records them so
nobody repeats the sequence:
services: not resolvable by name from a host-networked
job — "getaddrinfo EAI_AGAIN postgres"
--network container:$HOSTNAME /etc/hostname is the HOST's name, not a
container id, so the join finds nothing
default-gateway addressing wrong idea outright: with host networking the
default route is the real router, not a bridge
Sharing the host's network namespace means a published port is just on
127.0.0.1. Readiness is now checked over TCP from the job itself rather than
with pg_isready inside the container — the latter proves the server started,
not that this job can reach it, which is the thing that actually failed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+31
-23
@@ -30,21 +30,25 @@ jobs:
|
|||||||
verify:
|
verify:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# Postgres is started as a step rather than through `services:`.
|
# Postgres is started as a step rather than through `services:`, because
|
||||||
|
# this runner is configured with `container.network: host`.
|
||||||
#
|
#
|
||||||
# Two approaches were tried and rejected before this one:
|
# That single setting explains three failed attempts, and is worth writing
|
||||||
|
# down so nobody repeats them:
|
||||||
#
|
#
|
||||||
# `services:` — this runner does not attach service
|
# `services:` Service containers are not resolvable by
|
||||||
# containers to the job network, giving
|
# name from a host-networked job, giving
|
||||||
# "getaddrinfo EAI_AGAIN postgres".
|
# "getaddrinfo EAI_AGAIN postgres".
|
||||||
# `--network container:$HOSTNAME` — /etc/hostname inside the job reports
|
# `--network container:$HOSTNAME` /etc/hostname reports the HOST's name,
|
||||||
# the HOST's name, not the container id,
|
# not a container id, so the namespace
|
||||||
# so the namespace join finds no such
|
# join finds no such container.
|
||||||
# container.
|
# default-gateway addressing The wrong idea entirely: with host
|
||||||
|
# networking the default route is the
|
||||||
|
# real router, not a docker bridge.
|
||||||
#
|
#
|
||||||
# What works without needing to know our own container id: publish the port
|
# Because the job shares the host's network namespace, a published port is
|
||||||
# on the host and reach it through the job container's default gateway. The
|
# simply on 127.0.0.1. The port is derived from the run id so concurrent
|
||||||
# port is derived from the run id so concurrent runs cannot collide.
|
# runs cannot collide.
|
||||||
env:
|
env:
|
||||||
PG_CONTAINER: pig-ci-pg-${{ github.run_id }}
|
PG_CONTAINER: pig-ci-pg-${{ github.run_id }}
|
||||||
|
|
||||||
@@ -57,29 +61,33 @@ jobs:
|
|||||||
|
|
||||||
- name: Start Postgres
|
- name: Start Postgres
|
||||||
run: |
|
run: |
|
||||||
# A per-run port in the ephemeral range, so two runs never collide.
|
|
||||||
PG_PORT=$(( 45000 + (${{ github.run_id }} % 15000) ))
|
PG_PORT=$(( 45000 + (${{ github.run_id }} % 15000) ))
|
||||||
GATEWAY=$(ip route | awk '/^default/ {print $3; exit}')
|
echo "Publishing Postgres on 127.0.0.1:${PG_PORT}"
|
||||||
if [ -z "$GATEWAY" ]; then echo "Could not determine the gateway"; exit 1; fi
|
|
||||||
echo "Postgres will be published on ${GATEWAY}:${PG_PORT}"
|
|
||||||
|
|
||||||
docker rm -f "$PG_CONTAINER" 2>/dev/null || true
|
docker rm -f "$PG_CONTAINER" 2>/dev/null || true
|
||||||
docker run -d --name "$PG_CONTAINER" \
|
docker run -d --name "$PG_CONTAINER" \
|
||||||
-p "${PG_PORT}:5432" \
|
-p "127.0.0.1:${PG_PORT}:5432" \
|
||||||
-e POSTGRES_USER=pig -e POSTGRES_PASSWORD=pig -e POSTGRES_DB=pig \
|
-e POSTGRES_USER=pig -e POSTGRES_PASSWORD=pig -e POSTGRES_DB=pig \
|
||||||
postgres:16-alpine
|
postgres:16-alpine
|
||||||
|
|
||||||
|
# pg_isready inside the container only proves the server started.
|
||||||
|
# What matters is that THIS job can reach it through the published
|
||||||
|
# port, so the readiness check is made from here, over TCP.
|
||||||
for i in $(seq 1 60); do
|
for i in $(seq 1 60); do
|
||||||
if docker exec "$PG_CONTAINER" pg_isready -U pig -q; then
|
if node -e "
|
||||||
echo "Postgres ready after ${i}s"
|
const net=require('net');
|
||||||
# Export for every later step.
|
const s=net.connect(${PG_PORT},'127.0.0.1');
|
||||||
echo "DATABASE_URL=postgres://pig:pig@${GATEWAY}:${PG_PORT}/pig" >> "$GITHUB_ENV"
|
s.on('connect',()=>{s.end();process.exit(0)});
|
||||||
|
s.on('error',()=>process.exit(1));
|
||||||
|
" 2>/dev/null; then
|
||||||
|
echo "Reachable after ${i}s"
|
||||||
|
echo "DATABASE_URL=postgres://pig:pig@127.0.0.1:${PG_PORT}/pig" >> "$GITHUB_ENV"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
echo "Postgres did not become ready"
|
echo "Postgres never became reachable on 127.0.0.1:${PG_PORT}"
|
||||||
docker logs "$PG_CONTAINER" | tail -30
|
docker logs "$PG_CONTAINER" 2>&1 | tail -30
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
|
|||||||
Reference in New Issue
Block a user