Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f8afa2f28 |
+8
-3
@@ -276,10 +276,15 @@ ENV BIND_ADDRESS=0.0.0.0
|
|||||||
|
|
||||||
# The probe requests the container's own address, not loopback, so a
|
# The probe requests the container's own address, not loopback, so a
|
||||||
# listener that answers only on loopback (an empty or loopback
|
# listener that answers only on loopback (an empty or loopback
|
||||||
# BIND_ADDRESS) fails it. hostname -i prints one IPv4 address per
|
# BIND_ADDRESS) fails it. On a network with IPv4, hostname -i prints the
|
||||||
# network the container is on; the first is enough.
|
# container's IPv4 address, which 0.0.0.0 and :: both answer. On an
|
||||||
|
# IPv6-only network it prints none, and the probe requests the IPv6
|
||||||
|
# address /etc/hosts lists under the container's hostname instead, which
|
||||||
|
# only :: answers.
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
CMD wget --no-verbose --tries=1 --spider "http://$(hostname -i | cut -d ' ' -f 1):8080/.well-known/healthcheck" || exit 1
|
CMD addr="$(hostname -i 2>/dev/null | cut -d ' ' -f 1)"; \
|
||||||
|
[ -n "$addr" ] || addr="[$(getent hosts "$(hostname)" | cut -d ' ' -f 1)]"; \
|
||||||
|
wget --no-verbose --tries=1 --spider "http://$addr:8080/.well-known/healthcheck" || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||||
CMD ["/app/webhooker"]
|
CMD ["/app/webhooker"]
|
||||||
|
|||||||
@@ -310,7 +310,9 @@ the namespace, whatever the process bound. Exposure is decided at the publish
|
|||||||
flag instead — `-p 127.0.0.1:8080:8080` rather than `-p 8080:8080` — which is
|
flag instead — `-p 127.0.0.1:8080:8080` rather than `-p 8080:8080` — which is
|
||||||
the operator's to choose and is what [Running with Docker](#running-with-docker)
|
the operator's to choose and is what [Running with Docker](#running-with-docker)
|
||||||
shows. A loopback bind inside a container buys nothing and makes the process
|
shows. A loopback bind inside a container buys nothing and makes the process
|
||||||
unreachable through its own published port.
|
unreachable through its own published port. A container on an IPv6-only Docker
|
||||||
|
network has no IPv4 address for `0.0.0.0` to answer on, so there it needs
|
||||||
|
`BIND_ADDRESS=::`.
|
||||||
|
|
||||||
The value must be an IP address literal:
|
The value must be an IP address literal:
|
||||||
|
|
||||||
@@ -668,9 +670,9 @@ Either set a literal or leave the variable out entirely.
|
|||||||
Overriding `BIND_ADDRESS` to a loopback address in a container — by that route
|
Overriding `BIND_ADDRESS` to a loopback address in a container — by that route
|
||||||
or deliberately — makes the container unreachable from outside its namespace
|
or deliberately — makes the container unreachable from outside its namespace
|
||||||
even with `-p`. The published port answers nothing, and the health check fails
|
even with `-p`. The published port answers nothing, and the health check fails
|
||||||
too: it requests the container's own address (the first one `hostname -i`
|
too: it requests the container's own address (its IPv4 address, or its IPv6
|
||||||
prints), where a loopback bind is not listening. The container then goes
|
address on an IPv6-only network), where a loopback bind is not listening. The
|
||||||
`unhealthy` about **65 seconds** after start — from
|
container then goes `unhealthy` about **65 seconds** after start — from
|
||||||
`HEALTHCHECK --start-period=5s --interval=30s --retries=3`, so failing probes at
|
`HEALTHCHECK --start-period=5s --interval=30s --retries=3`, so failing probes at
|
||||||
5s, 35s and 65s, and `unhealthy` on the third. (Docker's probe cadence during
|
5s, 35s and 65s, and `unhealthy` on the third. (Docker's probe cadence during
|
||||||
the start period has changed between versions; re-derive from those three values
|
the start period has changed between versions; re-derive from those three values
|
||||||
|
|||||||
@@ -317,34 +317,43 @@ func TestBindAddress_WildcardReachesOtherAddresses(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestBindAddress_WildcardV4IsIPv4Only pins that 0.0.0.0 answers on
|
// TestBindAddress_WildcardV4IsIPv4Only pins that 0.0.0.0 answers on
|
||||||
// IPv4 alone, as the README says. Go listens on 0.0.0.0 with a socket
|
// IPv4 alone, as the README says, whether it is written as 0.0.0.0 or
|
||||||
// that answers IPv6 too unless told otherwise, which put the admin UI
|
// as the IPv4-mapped ::ffff:0.0.0.0. Go listens on 0.0.0.0 with a
|
||||||
// and the receiver on every IPv6 address of a host whose operator had
|
// socket that answers IPv6 too unless told otherwise, which put the
|
||||||
// chosen an IPv4 bind.
|
// admin UI and the receiver on every IPv6 address of a host whose
|
||||||
|
// operator had chosen an IPv4 bind.
|
||||||
func TestBindAddress_WildcardV4IsIPv4Only(t *testing.T) {
|
func TestBindAddress_WildcardV4IsIPv4Only(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
skipWithoutIPv6(t)
|
skipWithoutIPv6(t)
|
||||||
|
|
||||||
port := startBoundServer(t, wildcardV4)
|
for _, bindAddress := range []string{wildcardV4, "::ffff:0.0.0.0"} {
|
||||||
|
t.Run(bindAddress, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
// Positive control: the service really is up, on IPv4.
|
port := startBoundServer(t, bindAddress)
|
||||||
requireDialable(t, net.JoinHostPort(loopbackV4, strconv.Itoa(port)))
|
|
||||||
|
|
||||||
dialer := net.Dialer{Timeout: dialTimeout}
|
// Positive control: the service really is up, on IPv4.
|
||||||
|
requireDialable(
|
||||||
|
t, net.JoinHostPort(loopbackV4, strconv.Itoa(port)),
|
||||||
|
)
|
||||||
|
|
||||||
conn, err := dialer.DialContext(
|
dialer := net.Dialer{Timeout: dialTimeout}
|
||||||
t.Context(), "tcp",
|
|
||||||
net.JoinHostPort(loopbackV6, strconv.Itoa(port)),
|
conn, err := dialer.DialContext(
|
||||||
)
|
t.Context(), "tcp",
|
||||||
if err == nil {
|
net.JoinHostPort(loopbackV6, strconv.Itoa(port)),
|
||||||
_ = conn.Close()
|
)
|
||||||
|
if err == nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ErrorIs(
|
||||||
|
t, err, syscall.ECONNREFUSED,
|
||||||
|
"a %s bind answered on %s", bindAddress, loopbackV6,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
require.ErrorIs(
|
|
||||||
t, err, syscall.ECONNREFUSED,
|
|
||||||
"a %s bind answered on %s", wildcardV4, loopbackV6,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestBindAddress_WildcardV6ReachesBothFamilies is the counterpart:
|
// TestBindAddress_WildcardV6ReachesBothFamilies is the counterpart:
|
||||||
|
|||||||
@@ -46,10 +46,12 @@ func (s *Server) listenAddr() string {
|
|||||||
//
|
//
|
||||||
// On "tcp", Go binds 0.0.0.0 with a socket that answers IPv6 as well,
|
// On "tcp", Go binds 0.0.0.0 with a socket that answers IPv6 as well,
|
||||||
// exactly as it binds ::. "tcp4" keeps 0.0.0.0 to IPv4, which is what
|
// exactly as it binds ::. "tcp4" keeps 0.0.0.0 to IPv4, which is what
|
||||||
// the README says it means. :: stays on "tcp" and answers both.
|
// the README says it means. :: stays on "tcp" and answers both. An
|
||||||
|
// IPv4-mapped address such as ::ffff:0.0.0.0 is the IPv4 address it
|
||||||
|
// carries to Go's listener, so it gets "tcp4" too.
|
||||||
func (s *Server) listenNetwork() string {
|
func (s *Server) listenNetwork() string {
|
||||||
addr, err := netip.ParseAddr(s.params.Config.BindAddress)
|
addr, err := netip.ParseAddr(s.params.Config.BindAddress)
|
||||||
if err == nil && addr.Is4() {
|
if err == nil && addr.Unmap().Is4() {
|
||||||
return "tcp4"
|
return "tcp4"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user