Refactor Dockerfile for multi-stage build and runtime optimization
- Implemented a two-stage Dockerfile: build environment with Python dependencies and lean runtime stage with HAProxy. - Added custom entrypoint script for dynamic Docker group adjustment. - Improved HAProxy command generation by introducing a static method for binary resolution. - Updated tests and functional logic to dynamically resolve the `haproxy` binary path. - Adjusted file permissions in E2E key generation script for better security. - Streamlined `.gitignore` by removing unnecessary exclusions.
This commit is contained in:
parent
bfd38adea5
commit
44cb3dd5e0
7 changed files with 65 additions and 77 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -12,8 +12,6 @@ __pycache__
|
|||
|
||||
# Build artifacts
|
||||
dist/
|
||||
build/
|
||||
!build/assets/
|
||||
*.egg-info/
|
||||
|
||||
/tests_e2e/static/conf/config.yml
|
||||
|
|
|
|||
|
|
@ -1,16 +1,39 @@
|
|||
FROM alpine:3.23
|
||||
# ==============================================================================
|
||||
# Stage 1: Build Python virtual environment and run tests
|
||||
# ==============================================================================
|
||||
FROM haproxy:3.3-alpine AS builder
|
||||
|
||||
USER root
|
||||
|
||||
RUN apk add --no-cache python3 bash curl build-base python3-dev musl-dev linux-headers \
|
||||
&& curl -LsSf https://astral.sh/uv/install.sh | sh \
|
||||
&& ln -s /root/.local/bin/uv /usr/local/bin/uv
|
||||
|
||||
WORKDIR /scripts
|
||||
|
||||
COPY pyproject.toml uv.lock LICENSE README.md ./
|
||||
COPY src/ ./src/
|
||||
COPY tests/ ./tests/
|
||||
|
||||
RUN uv sync --frozen
|
||||
RUN uv run pytest -s -vv tests/
|
||||
RUN uv sync --no-dev
|
||||
RUN rm -rf tests/
|
||||
|
||||
# ==============================================================================
|
||||
# Stage 2: Lean runtime image
|
||||
# ==============================================================================
|
||||
FROM haproxy:3.3-alpine
|
||||
|
||||
ARG RELEASE_VERSION_ARG
|
||||
|
||||
ENV RELEASE_VERSION=$RELEASE_VERSION_ARG
|
||||
ENV TZ="Etc/UTC"
|
||||
|
||||
RUN apk add --no-cache haproxy bash python3 certbot openssl curl \
|
||||
&& apk add --no-cache --virtual .build-deps build-base python3-dev musl-dev linux-headers \
|
||||
&& curl -LsSf https://astral.sh/uv/install.sh | sh \
|
||||
&& ln -s /root/.local/bin/uv /usr/local/bin/uv
|
||||
USER root
|
||||
|
||||
RUN mkdir -p /etc/easyhaproxy/haproxy \
|
||||
RUN apk add --no-cache certbot openssl bash curl su-exec \
|
||||
&& mkdir -p /etc/easyhaproxy/haproxy \
|
||||
&& openssl dhparam -out /etc/easyhaproxy/haproxy/dhparam 2048 \
|
||||
&& openssl dhparam -out /etc/easyhaproxy/haproxy/dhparam-1024 1024 \
|
||||
&& mkdir -p /etc/easyhaproxy/certs/certbot /etc/easyhaproxy/certs/haproxy \
|
||||
|
|
@ -22,18 +45,12 @@ RUN mkdir -p /etc/easyhaproxy/haproxy \
|
|||
&& cat /tmp/placeholder.crt /tmp/placeholder.key > /etc/easyhaproxy/certs/haproxy/placeholder.pem \
|
||||
&& rm /tmp/placeholder.key /tmp/placeholder.crt
|
||||
|
||||
WORKDIR /scripts
|
||||
|
||||
COPY build/assets /
|
||||
COPY build/entrypoint.sh /entrypoint.sh
|
||||
COPY --from=builder /scripts /scripts
|
||||
|
||||
COPY pyproject.toml uv.lock LICENSE README.md /scripts/
|
||||
COPY src/ /scripts/src/
|
||||
COPY tests/ /scripts/tests/
|
||||
RUN chmod +x /entrypoint.sh \
|
||||
&& chown -R haproxy:haproxy /etc/easyhaproxy /scripts
|
||||
|
||||
RUN cd /scripts && uv sync --frozen
|
||||
|
||||
RUN apk del .build-deps
|
||||
|
||||
RUN cd /scripts && uv run pytest -s -vv tests/ && uv sync --no-dev
|
||||
|
||||
CMD ["/scripts/.venv/bin/easy-haproxy"]
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["--base-path", "/etc/easyhaproxy"]
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDyTCCArGgAwIBAgIUVpIokbXupa29drpQBmRlKTViM+UwDQYJKoZIhvcNAQEL
|
||||
BQAwdDELMAkGA1UEBhMCQVUxFTATBgNVBAgMDFBsYWNlIEhvbGRlcjEVMBMGA1UE
|
||||
BwwMUGxhY2UgSG9sZGVyMSEwHwYDVQQKDBhQbGFjZSBIb2xkZXIgQ2VydGlmaWNh
|
||||
dGUxFDASBgNVBAMMC2V4YW1wbGUub3JnMB4XDTIyMDgxNTA0NTEwMFoXDTMyMDgx
|
||||
MjA0NTEwMFowdDELMAkGA1UEBhMCQVUxFTATBgNVBAgMDFBsYWNlIEhvbGRlcjEV
|
||||
MBMGA1UEBwwMUGxhY2UgSG9sZGVyMSEwHwYDVQQKDBhQbGFjZSBIb2xkZXIgQ2Vy
|
||||
dGlmaWNhdGUxFDASBgNVBAMMC2V4YW1wbGUub3JnMIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEAqufw4FdqYcJep7mHHcYGUN79GNBLpvAIdg+1NbKx+cB/
|
||||
PtaDuozqVkkT8CmM0Mruay4vCbYkMytCeKgHj2+hLMy7oUQvx2pK/V0i0foPAC0m
|
||||
gAvgmaWZbQTENHX4A0Rwvim0yixgeBVhz4hTMOIunilSXbKRkFBUidCnYQe1Nzy1
|
||||
dbH/fh8++fzLCglDE2kydrE3Zq/54G2xFOxPt1DZRnQ3RBYaMIR/uPPjpVxRWl+p
|
||||
w4ucklAIZcu2htlOpGl7/3baMtnhpTo9LrkWzSNS7CJQvj6BvDULbcN3+hNPOoVE
|
||||
MM9MCaM9V+FS25kf+DfyaVUVDVIv0thpwa+f3tCXSwIDAQABo1MwUTAdBgNVHQ4E
|
||||
FgQUV831A1qfpV+Sy/J+lN6LKLgopsQwHwYDVR0jBBgwFoAUV831A1qfpV+Sy/J+
|
||||
lN6LKLgopsQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAhnf7
|
||||
aW/jpr8JWC0l2lo0z1HpOaSNehhcfNyM6Y43lR3Lr1avibbIXkAOzCuSWanFNnex
|
||||
dOq8PbG9bmO1ncM6qzXRBzk4pLVJmzzDZs/fPVghSZumY2bzzJFtdCQ5FiLaaE/c
|
||||
cKtBPvoUjfvrBU9OwFSb9UaoQdxtateb/Kk6JfzWi6YZqxSXFNa0ZTWJaoRFQVj5
|
||||
bZZe+wgpGRz46p+2YMwsNolXxa+7yY9x6kOMqZP6++5LZGXm5iWxjWbN4WtqmNnN
|
||||
cK35fAdLlg4d3wn5tVuTkpKH0FcaRlgpBSjVKejgnFTCxKcOwOCqOfu+nuSSWpa7
|
||||
aRqdAbMTeVFQhyLXhQ==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCq5/DgV2phwl6n
|
||||
uYcdxgZQ3v0Y0Eum8Ah2D7U1srH5wH8+1oO6jOpWSRPwKYzQyu5rLi8JtiQzK0J4
|
||||
qAePb6EszLuhRC/Hakr9XSLR+g8ALSaAC+CZpZltBMQ0dfgDRHC+KbTKLGB4FWHP
|
||||
iFMw4i6eKVJdspGQUFSJ0KdhB7U3PLV1sf9+Hz75/MsKCUMTaTJ2sTdmr/ngbbEU
|
||||
7E+3UNlGdDdEFhowhH+48+OlXFFaX6nDi5ySUAhly7aG2U6kaXv/dtoy2eGlOj0u
|
||||
uRbNI1LsIlC+PoG8NQttw3f6E086hUQwz0wJoz1X4VLbmR/4N/JpVRUNUi/S2GnB
|
||||
r5/e0JdLAgMBAAECggEAI0nY9rmWAbF8ke1A9OjajQA+Ck2YEVQmqxn7NKc9EHCq
|
||||
1XK9qFtIV6CnOUObC9GbAQ58L+kn+FjKVNd9GCTYhsOPSnEl3GsaKM5+ThTv2/12
|
||||
oaHSMmd7EoOVb6+cEjCjhuBdsBERqjngBFYFt2Y8cfPeSfKBE+dCTWKD7QkGZe0Q
|
||||
tEII2NeE1AoQwG34TANxyn+HbZG809i+k21Pm0tPNyAFwhPvotpjSftMNgco8jcx
|
||||
39DCt7rG1etpc4VhIUUl8GqsNAHYg8/SfscE18PYeX7RJpKGS128E1mKCqWqn+Ie
|
||||
VCiMmWoQ4ZBlUFp3tS4+FWcQtDr8Z82QTZQn3ODwIQKBgQDV1NR+SxjBBnzpHDZC
|
||||
cTxCK0PU37VmPjyr6BAXlftCPLGLgKEVIDRuna6EoiZ0/K0RMDZRoaJgDQPoAXDV
|
||||
UwJOWYtlJBzUxwQbBJc3S7C3kEC+qeRT9lRwIrn7P6gT1DMcSRsV4ULwGRWAMa2Q
|
||||
6apbK/K+56Ds7LiStu3OcJSxDwKBgQDMnAvLm0uMo9ZceKG515ruqzQj2YPz2+Zc
|
||||
f38pD1hESDRj1bzgT09FAsejPnlN7KPp8TFgRUB9Rqb6DZCkx49zdtcDkuZIKfQH
|
||||
Ga7ITBXnTwe8M+nwq2Q2LJYPdB/p8mBqh4ujA2XIS7ZHCKqOFGsseP4H1uxZeORI
|
||||
pIPQp4C+BQKBgQDKHw9tAZc4feV8g4pWa6rF8ReBFKTnLFU1OXpckQybo7s/Xirl
|
||||
STfGh437GTq4wk7lPGlb6CkQGb1jhFkfjANWBBZbWDNYfXZIA6LcRdOY7+YDU5vc
|
||||
Ma/G/0xFTfqWI7LcPc44dGFNiqhkMJEbtYOuAnDGOzRGP8yIAhnvVUN3yQKBgQC1
|
||||
eaYglYGBoQMMi1Xt7iQVkbWyIkedr6l22wJe2aRRE7Wb4sQeM1m8fMWyrUOL8NpF
|
||||
MU6481NKibKp0AQ9kl5Sa9Iy8kTbNpKhBY93SbyXpwnWTDku4+UDA7KozDdOGVKY
|
||||
ydX45JeO+lAWWsJjOAsCq+Gr9F020jmvkHL1SsuuPQKBgQDHHCSOlUycLnzxEAks
|
||||
uiu5MseFzkmdWN1ShrjPkcJ4HLmSD5zRgCySpBB92WPU+yBANlY7WN6fJHVJk/d7
|
||||
sNAg78GuAxcWrNAQwu6DRjhnb8zTVICJX8HmDLmsoOHwLsShdckWMks5XEk3NzoV
|
||||
4ym6aG0tDX+rkBkP/VIjSXA+Cg==
|
||||
-----END PRIVATE KEY-----
|
||||
13
build/entrypoint.sh
Normal file
13
build/entrypoint.sh
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
## When docker.sock is mounted its GID comes from the host and is unknown at
|
||||
## build time. Detect it at startup and add the haproxy user to that group so
|
||||
## Docker/Swarm discovery works without running the whole process as root.
|
||||
#if [ -S /var/run/docker.sock ]; then
|
||||
# SOCK_GID=$(stat -c '%g' /var/run/docker.sock)
|
||||
# getent group "$SOCK_GID" >/dev/null 2>&1 || addgroup -g "$SOCK_GID" dockersock
|
||||
# addgroup haproxy "$(getent group "$SOCK_GID" | cut -d: -f1)" 2>/dev/null || true
|
||||
#fi
|
||||
|
||||
exec /scripts/.venv/bin/easy-haproxy "$@"
|
||||
|
|
@ -354,8 +354,12 @@ class DaemonizeHAProxy:
|
|||
self.thread = Process(target=self.__start, args=())
|
||||
self.thread.start()
|
||||
|
||||
@staticmethod
|
||||
def get_haproxy_bin() -> str:
|
||||
return shutil.which('haproxy') or '/usr/sbin/haproxy'
|
||||
|
||||
def get_haproxy_command(self, action, pid_file="/run/haproxy.pid"):
|
||||
haproxy_bin = shutil.which('haproxy') or '/usr/sbin/haproxy'
|
||||
haproxy_bin = DaemonizeHAProxy.get_haproxy_bin()
|
||||
custom_config_files = ""
|
||||
if len(list(self.get_custom_config_files().keys())) != 0:
|
||||
custom_config_files = f"-f {self.custom_config_folder}"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ from functions import Consts
|
|||
|
||||
from functions import DaemonizeHAProxy
|
||||
|
||||
BIN = DaemonizeHAProxy.get_haproxy_bin()
|
||||
|
||||
|
||||
def test_daemonize_haproxy():
|
||||
daemon = DaemonizeHAProxy()
|
||||
|
|
@ -16,12 +18,12 @@ def test_daemonize_haproxy_check_config():
|
|||
def test_daemonize_haproxy_get_haproxy_command_start():
|
||||
daemon = DaemonizeHAProxy()
|
||||
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_START)
|
||||
assert command == f"/usr/sbin/haproxy -W -f {Consts.haproxy_config} -p /run/haproxy.pid -S /var/run/haproxy.sock"
|
||||
assert command == f"{BIN} -W -f {Consts.haproxy_config} -p /run/haproxy.pid -S /var/run/haproxy.sock"
|
||||
|
||||
def test_daemonize_haproxy_get_haproxy_command_reload_nopid():
|
||||
daemon = DaemonizeHAProxy()
|
||||
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_RELOAD)
|
||||
assert command == f"/usr/sbin/haproxy -W -f {Consts.haproxy_config} -p /run/haproxy.pid -S /var/run/haproxy.sock"
|
||||
assert command == f"{BIN} -W -f {Consts.haproxy_config} -p /run/haproxy.pid -S /var/run/haproxy.sock"
|
||||
|
||||
def test_daemonize_haproxy_get_haproxy_command_reload_pidinvalid():
|
||||
daemon = DaemonizeHAProxy()
|
||||
|
|
@ -29,7 +31,7 @@ def test_daemonize_haproxy_get_haproxy_command_reload_pidinvalid():
|
|||
with open("/tmp/temp.pid", 'w') as file:
|
||||
file.write("-1001")
|
||||
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_RELOAD, "/tmp/temp.pid")
|
||||
assert command == f"/usr/sbin/haproxy -W -f {Consts.haproxy_config} -p /tmp/temp.pid -S /var/run/haproxy.sock"
|
||||
assert command == f"{BIN} -W -f {Consts.haproxy_config} -p /tmp/temp.pid -S /var/run/haproxy.sock"
|
||||
finally:
|
||||
assert not os.path.exists("/tmp/temp.pid")
|
||||
|
||||
|
|
@ -39,7 +41,7 @@ def test_daemonize_haproxy_get_haproxy_command_reload_existing_pin():
|
|||
with open("/tmp/temp.pid", 'w') as file:
|
||||
file.write("1")
|
||||
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_RELOAD, "/tmp/temp.pid")
|
||||
assert command == f"/usr/sbin/haproxy -W -f {Consts.haproxy_config} -p /tmp/temp.pid -x /var/run/haproxy.sock -sf 1"
|
||||
assert command == f"{BIN} -W -f {Consts.haproxy_config} -p /tmp/temp.pid -x /var/run/haproxy.sock -sf 1"
|
||||
finally:
|
||||
assert os.path.exists("/tmp/temp.pid")
|
||||
os.unlink("/tmp/temp.pid")
|
||||
|
|
@ -55,4 +57,4 @@ def test_daemonize_haproxy2_check_config():
|
|||
def test_daemonize_haproxy2_get_haproxy_command_start():
|
||||
daemon = DaemonizeHAProxy(os.path.abspath(os.path.dirname(__file__)) + '/fixtures')
|
||||
command = daemon.get_haproxy_command(DaemonizeHAProxy.HAPROXY_START)
|
||||
assert command == f"/usr/sbin/haproxy -W -f {Consts.haproxy_config} -f {os.path.dirname(__file__)}/fixtures -p /run/haproxy.pid -S /var/run/haproxy.sock"
|
||||
assert command == f"{BIN} -W -f {Consts.haproxy_config} -f {os.path.dirname(__file__)}/fixtures -p /run/haproxy.pid -S /var/run/haproxy.sock"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ openssl req -x509 -nodes -days 3650 -newkey rsa:4096 \
|
|||
-keyout "$SCRIPT_DIR/static/host1.local.pem" \
|
||||
-out "$SCRIPT_DIR/static/host1.local.pem" \
|
||||
-subj "/C=US/ST=State/L=City/O=Organization/CN=host1.local"
|
||||
chmod 644 "$SCRIPT_DIR/static/host1.local.pem"
|
||||
|
||||
# Copy to swarm directory
|
||||
cp "$SCRIPT_DIR/static/host1.local.pem" "$SCRIPT_DIR/swarm/certs/host1.local.pem"
|
||||
|
|
@ -40,6 +41,7 @@ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
|||
-keyout "$SCRIPT_DIR/docker/host2.local.pem" \
|
||||
-out "$SCRIPT_DIR/docker/host2.local.pem" \
|
||||
-subj "/C=US/ST=State/L=City/O=Organization/CN=host2.local"
|
||||
chmod 644 "$SCRIPT_DIR/docker/host2.local.pem"
|
||||
|
||||
# Copy to swarm directory
|
||||
cp "$SCRIPT_DIR/docker/host2.local.pem" "$SCRIPT_DIR/swarm/certs/host2.local.pem"
|
||||
|
|
@ -55,9 +57,11 @@ echo "Generating JWT RSA key pair (2048-bit)..."
|
|||
|
||||
# Generate private key
|
||||
openssl genrsa -out "$SCRIPT_DIR/docker/jwt_private.pem" 2048
|
||||
chmod 644 "$SCRIPT_DIR/docker/jwt_private.pem"
|
||||
|
||||
# Extract public key
|
||||
openssl rsa -in "$SCRIPT_DIR/docker/jwt_private.pem" -pubout -out "$SCRIPT_DIR/docker/jwt_pubkey.pem"
|
||||
chmod 644 "$SCRIPT_DIR/docker/jwt_pubkey.pem"
|
||||
|
||||
echo "✓ Created JWT key pair (2048-bit)"
|
||||
echo " - $SCRIPT_DIR/docker/jwt_private.pem (private key)"
|
||||
|
|
@ -72,6 +76,7 @@ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
|||
-keyout "$SCRIPT_DIR/docker/certs/haproxy/.place_holder_cert.pem" \
|
||||
-out "$SCRIPT_DIR/docker/certs/haproxy/.place_holder_cert.pem" \
|
||||
-subj "/C=US/ST=State/L=City/O=Organization/CN=placeholder"
|
||||
chmod 644 "$SCRIPT_DIR/docker/certs/haproxy/.place_holder_cert.pem"
|
||||
|
||||
echo "✓ Created placeholder certificate"
|
||||
echo " - $SCRIPT_DIR/docker/certs/haproxy/.place_holder_cert.pem"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue