Skip to content

Alpine images leak memory for each OpenSSL connection #34

@jgaskins

Description

@jgaskins

TL;DR

Every time an OpenSSL connection is made in a Crystal program built with the Alpine containers (tested on linux/arm64 images), there is a memory leak. I thought this was a Crystal bug at first, but changing the version of OpenSSL used in this repo's Alpine containers eliminated the leak.

Reproducing the bug

To reproduce this bug, you can use this Crystal file:

# src/http_get.cr
require "http/client"

loop { HTTP::Client.get "https://example.com" }

And build a container defined by this Dockerfile:

FROM 84codes/crystal:1.17.0-alpine AS builder

WORKDIR /build
COPY src/http_get.cr .
RUN crystal build --release --static --progress --stats http_get.cr -o http_get

FROM scratch

COPY --from=builder /build/http_get /
CMD ["/http_get"]

If I've copy/pasted this all from the right places, when you run that container, it will consume an ever-increasing amount of RAM.

Note that reusing TCP connections and/or using a connection pool mitigates this memory leak. It doesn't eliminate it, but there is a workaround as long as you're able to maintain long-lived connections to a low-cardinality set of remote hosts. For most of us, I assume that's true.

Solution (kinda)

Following this advice, I based another container image off of it using this Dockerfile:

ARG version=latest

FROM 84codes/crystal:$version-alpine AS builder

# The OpenSSL packages that come with Alpine have a memory leak, causing
RUN apk add curl perl linux-headers && \
    curl -Ls "https://github.com/openssl/openssl/releases/download/openssl-3.3.3/openssl-3.3.3.tar.gz" | tar xz && \
    cd openssl-3.3.3 && \
    ./Configure --openssldir=/etc/ssl && \
    make -j

FROM 84codes/crystal:$version-alpine

COPY --from=builder /openssl-3.3.3/*.so.3 /openssl-3.3.3/*.a /usr/lib/
COPY --from=builder /openssl-3.3.3/*.pc /usr/lib/pkgconfig/

I've published this container here. If you change the FROM ... AS builder line in the first Dockerfile above to the following, the memory leak disappears:

FROM jgaskins/crystal:1.17.0 AS builder

My container image isn't a drop-in replacement, though. It's just a proof of concept. Simply overwriting OpenSSL lib files, as you might imagine, breaks things like apk. I assume crystal can pull from a different OpenSSL installation than the one in /usr/lib, which would probably be a more useful solution.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions