Hello everyone. I have been banging my head against the wall all day yesterday and I finally decided to sell all my computer and tech stuff and I plan to leave the city Friday night to go and live in a small community farm where I am told I can earn my food and lodgings by doing field works (picking up potatoes and doing pest control by hand I am told, it's an organic farm).
Anyway, before closing that chapter of my life I would like to understand why I couldn't get a MySQL client to talk to a MySQL server running in a container behind Traefik2.
The client just hangs out, Wireshark shows that nothing is going on after the syn/ack handshake and I am dead inside now.
I set up a basic tcp echo server in the vain hope to rule out any Traefik misconfiguration. Alas that tcp echo server works as intended and parrots things I send him and though I have rarely used postgres I also can connect to a Postgres server running in a container behind Traefik. Over tee-see-pee.
Here are my precious files that all run with docker-compose, each service in its own folder (mysql
, traefik2
, postgres
and tcp-echo-server
) on a standard Debian installed in a VPS hosted in that cheese and wine country. It has an ipv4 address and some funny domain names but that's irrelevant to the matter at hand.
Traefik 2
Traefik2 docker-compose.yml
---
networks:
default:
external:
name: ${PREFIX}
services:
web:
container_name: ${PREFIX}
environment:
- OVH_APPLICATION_KEY=${OVH_APPLICATION_KEY}
- OVH_APPLICATION_SECRET=${OVH_APPLICATION_SECRET}
- OVH_CONSUMER_KEY=${OVH_CONSUMER_KEY}
- OVH_ENDPOINT=${OVH_ENDPOINT}
image: traefik:2.2.8
labels:
- traefik.enable=true
- traefik.http.middlewares.${PREFIX}-auth.basicauth.users=${AUTH}
- traefik.http.routers.traefik.entrypoints=https
- traefik.http.routers.traefik.middlewares=${PREFIX}-auth
- traefik.http.routers.traefik.rule=Host(`${HOST}`)
- traefik.http.routers.traefik.service=api@internal
ports:
- "80:80"
- "443:443"
- "3306:3306"
- "5432:5432"
- "9000:9000"
restart: always
volumes:
- ./acme.json:/acme.json
- ./dynamic.yml:/etc/traefik/dynamic.yml
- ./static.yml:/etc/traefik/traefik.yml
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
Traefik2 .env
AUTH=admin:$apr1$password
HOST=traefik2.example.com
OVH_ENDPOINT=ovh-eu
OVH_APPLICATION_KEY=ovh_application_key
OVH_APPLICATION_SECRET=ovh_application_secret_key
OVH_CONSUMER_KEY=ovh_consumer_keyr
PREFIX=traefik2
Traefik2 static.yml
---
api:
dashboard: true
certificatesresolvers:
le:
acme:
caserver: "https://acme-v02.api.letsencrypt.org/directory"
dnschallenge:
provider: ovh
email: mail@example.com
storage: /acme.json
entrypoints:
http:
address: :80
http:
redirections:
entrypoint:
permanent: true
to: https
scheme: https
https:
address: :443
http:
tls:
certresolver: le
domains:
- main: example.com
sans:
- "*.example.com"
mysql:
address: :3306
tcp-echo-server:
address: :9000
postgres:
address: :5432
log:
level: DEBUG
providers:
docker:
exposedbydefault: false
file:
filename: /etc/traefik/dynamic.yml
providersthrottleduration: 2s
Traefik2 and how it is run
me@vps:~/traefik2/ $ docker network create traefik2
me@vps:~/traefik2/ $ docker-compose up -d
Traefik dashboard is now available on https://traefik2.example.com
TCP echo server
TCP echo server docker-compose.yml
A nice and functional tcp echo server written in Go that was easier to setup than classic nc
.
---
networks:
default:
external:
name: ${DOCKER_PREFIX}
services:
app:
container_name: ${DOCKER_PREFIX}-app
hostname: ${DOCKER_PREFIX}-app
image: venilnoronha/tcp-echo-server:latest
labels:
- traefik.enable=true
- traefik.tcp.routers.${DOCKER_PREFIX}-app.entrypoints=tcp-echo-server
- traefik.tcp.routers.${DOCKER_PREFIX}-app.rule=HostSNI(`*`)
- traefik.tcp.services.${DOCKER_PREFIX}-service.loadbalancer.server.port=9000
version: "3.4"
TCP echo server .env
file
DOCKER_PREFIX=tcp-echo-server
TCP echo server and how it is run
me@vps:~/tcp-echo-server/ $ docker network create tcp-echo-server
me@vps:~/tcp-echo-server/ $ docker network connect tcp-echo-server traefik2
me@vps:~/tcp-echo-server/ $ docker-compose up -d
Hey, it works ! Running something like:
me@home:~ $ echo 'world'|nc 192.0.2.2 9000
returns hello world
(the echo server adds an hello
prefix). TCP reverse proxying ! w00t !
Postgres server
Now that we got basic TCP data flow going on let's try with something more beefy (disclaimer: I am a liar, all those stunts were performed in reverse order during the investigation).
Postgres docker-compose.yml
---
networks:
default:
external:
name: ${DOCKER_PREFIX}
services:
db:
container_name: ${DOCKER_PREFIX}-db
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
hostname: ${DOCKER_PREFIX}-db
image: postgres
labels:
- traefik.enable=true
- traefik.tcp.routers.${DOCKER_PREFIX}-db.entrypoints=postgres
- traefik.tcp.routers.${DOCKER_PREFIX}-db.rule=HostSNI(`*`)
- traefik.tcp.routers.${DOCKER_PREFIX}-db.service=${DOCKER_PREFIX}-service
- traefik.tcp.services.${DOCKER_PREFIX}-service.loadbalancer.server.port=5432
volumes:
- db:/var/lib/postgresql/data
version: "3.4"
volumes:
db:
name: ${DOCKER_PREFIX}-db
Postgres .env
file
POSTGRES_PASSWORD=postgres
DOCKER_PREFIX=postgres
Postgres and how it is run
me@vps:~/postgres $ docker network create postgres
me@vps:~/postgres $ docker network connect postgres traefik2
me@vps:~/postgres $ docker-compose up -d
Connecting with:
me@home:~ $ pgcli --host 192.0.2.2 --port 5432 --username postgres postgres
gives me a nice shell:
Password:
Version: 1.6.0
Chat: https://gitter.im/dbcli/pgcli
Mail: https://groups.google.com/forum/#!forum/pgcli
Home: http://pgcli.com
I made a liittle dance at this point ^^. So now I have a server running in a container behind traefik2 and data is flowing through TCP ! w00t ! w00t !
Mysql server
So, let's set up that MySQL server now... Should basically be the same thing, right ?
Mysql docker-compose.yml
---
networks:
default:
external:
name: ${DOCKER_PREFIX}
services:
db:
container_name: ${DOCKER_PREFIX}-db
environment:
MYSQL_DATABASE: ${DOCKER_PREFIX}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_USER: ${MYSQL_USER}
hostname: ${DOCKER_PREFIX}-db
image: mysql:8
labels:
- traefik.enable=true
- traefik.tcp.routers.${DOCKER_PREFIX}-db.entrypoints=mysql
- traefik.tcp.routers.${DOCKER_PREFIX}-db.rule=HostSNI(`*`)
- traefik.tcp.routers.${DOCKER_PREFIX}-db.service=${DOCKER_PREFIX}-service
- traefik.tcp.services.${DOCKER_PREFIX}-service.loadbalancer.server.port=3306
volumes:
- db:/var/lib/mysql
version: "3.4"
volumes:
db:
name: ${DOCKER_PREFIX}-db
Notice how I am setting up the mysql-db
service manually. That's because I read that the MySQL image exposes both 3306
and 33060
ports and that Traefik2 doesn't know what to do about that (I don't really get it since the loadbalancer.server.port tag redirects traffic to the 3306 port anyway but okay). I read about it in that thread everyone reads (Can't connect to MySQL server: Connection refused) when they try to make MySQL works in a container behind Traefik2.
I didn't do that for postgres
and it works fine anyway. Whatever.
Mysql .env
file
MYSQL_ROOT_PASSWORD=mysql_root_password
MYSQL_USER=mysql_user
MYSQL_PASSWORD=mysql_password
DOCKER_PREFIX=mysql
URL=mysql.example.com
MySQL and how it is run
me@vps:~/mysql $ docker network create mysql
me@vps:~/mysql $ docker network connect mysql traefik2
me@vps:~/mysql $ docker-compose up -d
Let's connect:
me@home:~ $ mysql --host 192.0.2.2 --protocol=tcp --port 3306 --user mysql_user -pmysql_password
Let's wait a gazillion years.
Ten minutes later the wireshark logging window is frozen in time:
86 14.712241694 192.168.1.56 192.0.02.2 TCP 74 54258 → 3306 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=2038950184 TSecr=0 WS=128
87 14.732868264 192.0.02.2 192.168.1.56 TCP 74 3306 → 54258 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1452 SACK_PERM=1 TSval=535582122 TSecr=2038950184 WS=128
88 14.732971095 192.168.1.56 192.0.02.2 TCP 66 54258 → 3306 [ACK] Seq=1 Ack=1 Win=64256 Len=0 TSval=2038950205 TSecr=535582122
What I tried
- Following that other topic Can't connect to MySQL server: Connection refused I played around with explicitly adding the mysql service to 3306 (see the difference between postgres and mysql docker-compose).
- I checked I could connect to that container by binding the container's 3306 port to the host's 3306's port, thus bypassing Traefik2. It works.
- I checked I could connect from another container running in the same docker network. It works.
- I tried to disable the 33060 port (which is actually for an X connection): it doesn't work.
- I configured mysql to bind to
0.0.0.0.0
: it doesn't work.
I took a look at iptables
but I think it's fine (it can connect when not proxying anyway):
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy DROP)
target prot opt source destination
DOCKER-USER all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-1 all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain DOCKER (17 references)
target prot opt source destination
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:9000
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:afs3-rmtsys
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:afs3-update
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:afs3-bos
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:afs3-errors
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:afs3-volser
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:afs3-kaserver
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:afs3-vlserver
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:afs3-prserver
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:afs3-callback
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:afs3-fileserver
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:postgresql
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:mysql
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:https
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:http
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target prot opt source destination
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
RETURN all -- anywhere anywhere
Chain DOCKER-ISOLATION-STAGE-2 (17 references)
target prot opt source destination
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
RETURN all -- anywhere anywhere
Chain DOCKER-USER (1 references)
target prot opt source destination
RETURN all -- anywhere anywhere
I don't need no TLS at the moment. This is a task I'll lay down with my shame at the feet of my sons and daugthers, hoping they can achieve what I couldn't.
If anyone could tell me why that TCP connection isn't working or what's wrong with the mysql config (forgot to mention I tried with mariadb:latest and mysql5 and 8) that would be, like, really cool.
Maybe it only works with TLS, I don't know. Help.