├── Dockerfile ├── Makefile ├── README.md ├── entrypoint ├── README.md ├── docker-entrypoint.patch ├── modified.sh └── original.sh └── examples ├── kubernetes-rbac.yml └── kubernetes.yml /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rabbitmq:3.6-alpine 2 | 3 | RUN apk add --update curl && \ 4 | rm -rf /var/cache/apk/* 5 | 6 | ENV AUTOCLUSTER_VERSION=0.8.0 7 | 8 | RUN curl -sL -o /plugins/rabbitmq_aws-0.8.0.ez https://github.com/rabbitmq/rabbitmq-autocluster/releases/download/${AUTOCLUSTER_VERSION}/rabbitmq_aws-${AUTOCLUSTER_VERSION}.ez && \ 9 | curl -sL -o /plugins/autocluster-0.8.0.ez https://github.com/rabbitmq/rabbitmq-autocluster/releases/download/${AUTOCLUSTER_VERSION}/autocluster-${AUTOCLUSTER_VERSION}.ez 10 | 11 | RUN rabbitmq-plugins --offline enable rabbitmq_management rabbitmq_shovel rabbitmq_shovel_management autocluster 12 | 13 | COPY entrypoint/docker-entrypoint.patch /tmp 14 | RUN patch /usr/local/bin/docker-entrypoint.sh < /tmp/docker-entrypoint.patch 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | docker build -t foxylion/rabbitmq:3.6-autocluster . 4 | 5 | push: 6 | docker push foxylion/rabbitmq:3.6-autocluster 7 | 8 | deploy-kubernetes: 9 | kubectl apply -f examples/kubernetes.yml 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ autocluster image 2 | 3 | This Docker image has builtin the autocluster plugin and is configured to 4 | support a deployment in Kubernetes as a stateful set with persistent volumes 5 | attached to the pods. 6 | 7 | This ensures a highly available cluster which can recover, in case of a full outage, 8 | from persistence. 9 | 10 | See the Kubernetes example [here](https://github.com/foxylion/docker-rabbitmq/blob/master/examples/kubernetes.yml). 11 | 12 | To start this in your `minikube` cluster just call `kubectl apply -f examples/kubernetes.yml`. 13 | This will create a 3 node RabbitMQ cluster with persistence volumes attached to 14 | the storage. So even a StatefulSet scale to 0 the cluster will survive. 15 | 16 | The example configures the cluster to stop when a partition detects to be a minority 17 | and restarts after a majority is gained. More about this can be found in the RabbitMQ 18 | [documentation](https://www.rabbitmq.com/partitions.html#automatic-handling). 19 | 20 | This is important to know. Because it is not possible to simply scale down the 21 | StatefulSet from `7` to `3` nodes. You will need to slowly scale down the cluster 22 | and remove down nodes. Otherwise the resulting 3-node cluster will not be the 23 | majority of all defined nodes. 24 | 25 | ## Available docker image tags 26 | 27 | ### `3.6-autocluster` [![Docker Stars](https://img.shields.io/docker/stars/foxylion/rabbitmq.svg?style=flat-square)](https://hub.docker.com/r/foxylion/rabbitmq/) [![Docker Pulls](https://img.shields.io/docker/pulls/foxylion/rabbitmq.svg?style=flat-square)](https://hub.docker.com/r/foxylion/rabbitmq/) 28 | This is image based on `rabbitmq:3.6-alpine` with the following plugins 29 | enabled: `rabbitmq_management`, `rabbitmq_shovel`, `rabbitmq_shovel_management`, `autocluster` 30 | -------------------------------------------------------------------------------- /entrypoint/README.md: -------------------------------------------------------------------------------- 1 | The docker-entrypoint.sh file needs to be modified. Otherwise it is not possible 2 | to set the `cluster_partition_handling` option via a environment variable. 3 | 4 | To simplify this process when the original docker-entrypoint.sh is modified this 5 | is applied using a patch file. 6 | 7 | Commands used to create the patch: 8 | ``` 9 | docker run --rm rabbitmq:3.6.12-alpine cat /usr/local/bin/docker-entrypoint.sh > original.sh 10 | cp original.sh modified.sh 11 | # Do the modifications 12 | diff -Naur original.sh modified.sh > docker-entrypoint.patch 13 | ``` 14 | -------------------------------------------------------------------------------- /entrypoint/docker-entrypoint.patch: -------------------------------------------------------------------------------- 1 | --- orig.sh 2017-09-14 22:42:05.210893044 +0200 2 | +++ modified.sh 2017-09-14 22:47:59.877681421 +0200 3 | @@ -65,6 +65,7 @@ 4 | default_vhost 5 | hipe_compile 6 | vm_memory_high_watermark 7 | + cluster_partition_handling 8 | ) 9 | fileConfigKeys=( 10 | management_ssl_cacertfile 11 | @@ -213,7 +214,7 @@ 12 | 13 | local rawVal= 14 | case "$conf" in 15 | - verify|fail_if_no_peer_cert|depth) 16 | + verify|fail_if_no_peer_cert|depth|cluster_partition_handling) 17 | [ "$val" ] || continue 18 | rawVal="$val" 19 | ;; 20 | -------------------------------------------------------------------------------- /entrypoint/modified.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # usage: file_env VAR [DEFAULT] 5 | # ie: file_env 'XYZ_DB_PASSWORD' 'example' 6 | # (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of 7 | # "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) 8 | file_env() { 9 | local var="$1" 10 | local fileVar="${var}_FILE" 11 | local def="${2:-}" 12 | if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then 13 | echo >&2 "error: both $var and $fileVar are set (but are exclusive)" 14 | exit 1 15 | fi 16 | local val="$def" 17 | if [ "${!var:-}" ]; then 18 | val="${!var}" 19 | elif [ "${!fileVar:-}" ]; then 20 | val="$(< "${!fileVar}")" 21 | fi 22 | export "$var"="$val" 23 | unset "$fileVar" 24 | } 25 | 26 | # allow the container to be started with `--user` 27 | if [[ "$1" == rabbitmq* ]] && [ "$(id -u)" = '0' ]; then 28 | if [ "$1" = 'rabbitmq-server' ]; then 29 | chown -R rabbitmq /var/lib/rabbitmq 30 | fi 31 | exec su-exec rabbitmq "$BASH_SOURCE" "$@" 32 | fi 33 | 34 | # backwards compatibility for old environment variables 35 | : "${RABBITMQ_SSL_CERTFILE:=${RABBITMQ_SSL_CERT_FILE:-}}" 36 | : "${RABBITMQ_SSL_KEYFILE:=${RABBITMQ_SSL_KEY_FILE:-}}" 37 | : "${RABBITMQ_SSL_CACERTFILE:=${RABBITMQ_SSL_CA_FILE:-}}" 38 | 39 | # "management" SSL config should default to using the same certs 40 | : "${RABBITMQ_MANAGEMENT_SSL_CACERTFILE:=$RABBITMQ_SSL_CACERTFILE}" 41 | : "${RABBITMQ_MANAGEMENT_SSL_CERTFILE:=$RABBITMQ_SSL_CERTFILE}" 42 | : "${RABBITMQ_MANAGEMENT_SSL_KEYFILE:=$RABBITMQ_SSL_KEYFILE}" 43 | 44 | # Allowed env vars that will be read from mounted files (i.e. Docker Secrets): 45 | fileEnvKeys=( 46 | default_user 47 | default_pass 48 | ) 49 | 50 | # https://www.rabbitmq.com/configure.html 51 | sslConfigKeys=( 52 | cacertfile 53 | certfile 54 | depth 55 | fail_if_no_peer_cert 56 | keyfile 57 | verify 58 | ) 59 | managementConfigKeys=( 60 | "${sslConfigKeys[@]/#/ssl_}" 61 | ) 62 | rabbitConfigKeys=( 63 | default_pass 64 | default_user 65 | default_vhost 66 | hipe_compile 67 | vm_memory_high_watermark 68 | cluster_partition_handling 69 | ) 70 | fileConfigKeys=( 71 | management_ssl_cacertfile 72 | management_ssl_certfile 73 | management_ssl_keyfile 74 | ssl_cacertfile 75 | ssl_certfile 76 | ssl_keyfile 77 | ) 78 | allConfigKeys=( 79 | "${managementConfigKeys[@]/#/management_}" 80 | "${rabbitConfigKeys[@]}" 81 | "${sslConfigKeys[@]/#/ssl_}" 82 | ) 83 | 84 | declare -A configDefaults=( 85 | [management_ssl_fail_if_no_peer_cert]='false' 86 | [management_ssl_verify]='verify_none' 87 | 88 | [ssl_fail_if_no_peer_cert]='true' 89 | [ssl_verify]='verify_peer' 90 | ) 91 | 92 | haveConfig= 93 | haveSslConfig= 94 | haveManagementSslConfig= 95 | for fileEnvKey in "${fileEnvKeys[@]}"; do file_env "RABBITMQ_${fileEnvKey^^}"; done 96 | for conf in "${allConfigKeys[@]}"; do 97 | var="RABBITMQ_${conf^^}" 98 | val="${!var:-}" 99 | if [ "$val" ]; then 100 | if [ "${configDefaults[$conf]:-}" ] && [ "${configDefaults[$conf]}" = "$val" ]; then 101 | # if the value set is the same as the default, treat it as if it isn't set 102 | continue 103 | fi 104 | haveConfig=1 105 | case "$conf" in 106 | ssl_*) haveSslConfig=1 ;; 107 | management_ssl_*) haveManagementSslConfig=1 ;; 108 | esac 109 | fi 110 | done 111 | if [ "$haveSslConfig" ]; then 112 | missing=() 113 | for sslConf in cacertfile certfile keyfile; do 114 | var="RABBITMQ_SSL_${sslConf^^}" 115 | val="${!var}" 116 | if [ -z "$val" ]; then 117 | missing+=( "$var" ) 118 | fi 119 | done 120 | if [ "${#missing[@]}" -gt 0 ]; then 121 | { 122 | echo 123 | echo 'error: SSL requested, but missing required configuration' 124 | for miss in "${missing[@]}"; do 125 | echo " - $miss" 126 | done 127 | echo 128 | } >&2 129 | exit 1 130 | fi 131 | fi 132 | missingFiles=() 133 | for conf in "${fileConfigKeys[@]}"; do 134 | var="RABBITMQ_${conf^^}" 135 | val="${!var}" 136 | if [ "$val" ] && [ ! -f "$val" ]; then 137 | missingFiles+=( "$val ($var)" ) 138 | fi 139 | done 140 | if [ "${#missingFiles[@]}" -gt 0 ]; then 141 | { 142 | echo 143 | echo 'error: files specified, but missing' 144 | for miss in "${missingFiles[@]}"; do 145 | echo " - $miss" 146 | done 147 | echo 148 | } >&2 149 | exit 1 150 | fi 151 | 152 | # set defaults for missing values (but only after we're done with all our checking so we don't throw any of that off) 153 | for conf in "${!configDefaults[@]}"; do 154 | default="${configDefaults[$conf]}" 155 | var="RABBITMQ_${conf^^}" 156 | [ -z "${!var:-}" ] || continue 157 | eval "export $var=\"\$default\"" 158 | done 159 | 160 | # If long & short hostnames are not the same, use long hostnames 161 | if [ "$(hostname)" != "$(hostname -s)" ]; then 162 | : "${RABBITMQ_USE_LONGNAME:=true}" 163 | fi 164 | 165 | if [ "${RABBITMQ_ERLANG_COOKIE:-}" ]; then 166 | cookieFile='/var/lib/rabbitmq/.erlang.cookie' 167 | if [ -e "$cookieFile" ]; then 168 | if [ "$(cat "$cookieFile" 2>/dev/null)" != "$RABBITMQ_ERLANG_COOKIE" ]; then 169 | echo >&2 170 | echo >&2 "warning: $cookieFile contents do not match RABBITMQ_ERLANG_COOKIE" 171 | echo >&2 172 | fi 173 | else 174 | echo "$RABBITMQ_ERLANG_COOKIE" > "$cookieFile" 175 | chmod 600 "$cookieFile" 176 | fi 177 | fi 178 | 179 | # prints "$2$1$3$1...$N" 180 | join() { 181 | local sep="$1"; shift 182 | local out; printf -v out "${sep//%/%%}%s" "$@" 183 | echo "${out#$sep}" 184 | } 185 | indent() { 186 | if [ "$#" -gt 0 ]; then 187 | echo "$@" 188 | else 189 | cat 190 | fi | sed 's/^/\t/g' 191 | } 192 | rabbit_array() { 193 | echo -n '[' 194 | case "$#" in 195 | 0) echo -n ' ' ;; 196 | 1) echo -n " $1 " ;; 197 | *) 198 | local vals="$(join $',\n' "$@")" 199 | echo 200 | indent "$vals" 201 | esac 202 | echo -n ']' 203 | } 204 | rabbit_env_config() { 205 | local prefix="$1"; shift 206 | 207 | local ret=() 208 | local conf 209 | for conf; do 210 | local var="rabbitmq${prefix:+_$prefix}_$conf" 211 | var="${var^^}" 212 | 213 | local val="${!var:-}" 214 | 215 | local rawVal= 216 | case "$conf" in 217 | verify|fail_if_no_peer_cert|depth|cluster_partition_handling) 218 | [ "$val" ] || continue 219 | rawVal="$val" 220 | ;; 221 | 222 | hipe_compile) 223 | [ "$val" ] && rawVal='true' || rawVal='false' 224 | ;; 225 | 226 | cacertfile|certfile|keyfile) 227 | [ "$val" ] || continue 228 | rawVal='"'"$val"'"' 229 | ;; 230 | 231 | *) 232 | [ "$val" ] || continue 233 | rawVal='<<"'"$val"'">>' 234 | ;; 235 | esac 236 | [ "$rawVal" ] || continue 237 | 238 | ret+=( "{ $conf, $rawVal }" ) 239 | done 240 | 241 | join $'\n' "${ret[@]}" 242 | } 243 | 244 | shouldWriteConfig="$haveConfig" 245 | if [ ! -f /etc/rabbitmq/rabbitmq.config ]; then 246 | shouldWriteConfig=1 247 | fi 248 | 249 | if [ "$1" = 'rabbitmq-server' ] && [ "$shouldWriteConfig" ]; then 250 | fullConfig=() 251 | 252 | rabbitConfig=( 253 | "{ loopback_users, $(rabbit_array) }" 254 | ) 255 | 256 | # determine whether to set "vm_memory_high_watermark" (based on cgroups) 257 | memTotalKb= 258 | if [ -r /proc/meminfo ]; then 259 | memTotalKb="$(awk -F ':? +' '$1 == "MemTotal" { print $2; exit }' /proc/meminfo)" 260 | fi 261 | memLimitB= 262 | if [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then 263 | # "18446744073709551615" is a valid value for "memory.limit_in_bytes", which is too big for Bash math to handle 264 | # "$(( 18446744073709551615 / 1024 ))" = 0; "$(( 18446744073709551615 * 40 / 100 ))" = 0 265 | memLimitB="$(awk -v totKb="$memTotalKb" '{ 266 | limB = $0; 267 | limKb = limB / 1024; 268 | if (!totKb || limKb < totKb) { 269 | printf "%.0f\n", limB; 270 | } 271 | }' /sys/fs/cgroup/memory/memory.limit_in_bytes)" 272 | fi 273 | if [ -n "$memTotalKb" ] || [ -n "$memLimitB" ]; then 274 | # https://github.com/docker-library/rabbitmq/pull/105#issuecomment-242165822 275 | vmMemoryHighWatermark= 276 | if [ "${RABBITMQ_VM_MEMORY_HIGH_WATERMARK:-}" ]; then 277 | vmMemoryHighWatermark="$( 278 | awk -v lim="$memLimitB" ' 279 | /^[0-9]*[.][0-9]+$|^[0-9]+([.][0-9]+)?%$/ { 280 | perc = $0; 281 | if (perc ~ /%$/) { 282 | gsub(/%$/, "", perc); 283 | perc = perc / 100; 284 | } 285 | if (perc > 1.0 || perc <= 0.0) { 286 | printf "error: invalid percentage for vm_memory_high_watermark: %s (must be > 0%%, <= 100%%)\n", $0 > "/dev/stderr"; 287 | exit 1; 288 | } 289 | if (lim) { 290 | printf "{ absolute, %d }\n", lim * perc; 291 | } else { 292 | printf "%0.03f\n", perc; 293 | } 294 | next; 295 | } 296 | /^[0-9]+$/ { 297 | printf "{ absolute, %s }\n", $0; 298 | next; 299 | } 300 | /^[0-9]+([.][0-9]+)?[a-zA-Z]+$/ { 301 | printf "{ absolute, \"%s\" }\n", $0; 302 | next; 303 | } 304 | { 305 | printf "error: unexpected input for vm_memory_high_watermark: %s\n", $0; 306 | exit 1; 307 | } 308 | ' <(echo "$RABBITMQ_VM_MEMORY_HIGH_WATERMARK") 309 | )" 310 | elif [ -n "$memLimitB" ]; then 311 | # if there is a cgroup limit, default to 40% of _that_ (as recommended by upstream) 312 | vmMemoryHighWatermark="{ absolute, $(awk -v lim="$memLimitB" 'BEGIN { printf "%.0f\n", lim * 0.4; exit }') }" 313 | # otherwise let the default behavior win (40% of the total available) 314 | fi 315 | if [ "$vmMemoryHighWatermark" ]; then 316 | # https://www.rabbitmq.com/memory.html#memsup-usage 317 | rabbitConfig+=( "{ vm_memory_high_watermark, $vmMemoryHighWatermark }" ) 318 | fi 319 | elif [ "${RABBITMQ_VM_MEMORY_HIGH_WATERMARK:-}" ]; then 320 | echo >&2 'warning: RABBITMQ_VM_MEMORY_HIGH_WATERMARK was specified, but current system memory or cgroup memory limit cannot be determined' 321 | echo >&2 ' (so "vm_memory_high_watermark" will not be set)' 322 | fi 323 | 324 | if [ "$haveSslConfig" ]; then 325 | IFS=$'\n' 326 | rabbitSslOptions=( $(rabbit_env_config 'ssl' "${sslConfigKeys[@]}") ) 327 | unset IFS 328 | 329 | rabbitConfig+=( 330 | "{ tcp_listeners, $(rabbit_array) }" 331 | "{ ssl_listeners, $(rabbit_array 5671) }" 332 | "{ ssl_options, $(rabbit_array "${rabbitSslOptions[@]}") }" 333 | ) 334 | else 335 | rabbitConfig+=( 336 | "{ tcp_listeners, $(rabbit_array 5672) }" 337 | "{ ssl_listeners, $(rabbit_array) }" 338 | ) 339 | fi 340 | 341 | IFS=$'\n' 342 | rabbitConfig+=( $(rabbit_env_config '' "${rabbitConfigKeys[@]}") ) 343 | unset IFS 344 | 345 | fullConfig+=( "{ rabbit, $(rabbit_array "${rabbitConfig[@]}") }" ) 346 | 347 | # if management plugin is installed, generate config for it 348 | # https://www.rabbitmq.com/management.html#configuration 349 | if [ "$(rabbitmq-plugins list -m -e rabbitmq_management)" ]; then 350 | rabbitManagementConfig=() 351 | 352 | if [ "$haveManagementSslConfig" ]; then 353 | IFS=$'\n' 354 | rabbitManagementSslOptions=( $(rabbit_env_config 'management_ssl' "${sslConfigKeys[@]}") ) 355 | unset IFS 356 | 357 | rabbitManagementListenerConfig+=( 358 | '{ port, 15671 }' 359 | '{ ssl, true }' 360 | "{ ssl_opts, $(rabbit_array "${rabbitManagementSslOptions[@]}") }" 361 | ) 362 | else 363 | rabbitManagementListenerConfig+=( 364 | '{ port, 15672 }' 365 | '{ ssl, false }' 366 | ) 367 | fi 368 | rabbitManagementConfig+=( 369 | "{ listener, $(rabbit_array "${rabbitManagementListenerConfig[@]}") }" 370 | ) 371 | 372 | # if definitions file exists, then load it 373 | # https://www.rabbitmq.com/management.html#load-definitions 374 | managementDefinitionsFile='/etc/rabbitmq/definitions.json' 375 | if [ -f "${managementDefinitionsFile}" ]; then 376 | # see also https://github.com/docker-library/rabbitmq/pull/112#issuecomment-271485550 377 | rabbitManagementConfig+=( 378 | "{ load_definitions, \"$managementDefinitionsFile\" }" 379 | ) 380 | fi 381 | 382 | fullConfig+=( 383 | "{ rabbitmq_management, $(rabbit_array "${rabbitManagementConfig[@]}") }" 384 | ) 385 | fi 386 | 387 | echo "$(rabbit_array "${fullConfig[@]}")." > /etc/rabbitmq/rabbitmq.config 388 | fi 389 | 390 | combinedSsl='/tmp/combined.pem' 391 | if [ "$haveSslConfig" ] && [[ "$1" == rabbitmq* ]] && [ ! -f "$combinedSsl" ]; then 392 | # Create combined cert 393 | cat "$RABBITMQ_SSL_CERTFILE" "$RABBITMQ_SSL_KEYFILE" > "$combinedSsl" 394 | chmod 0400 "$combinedSsl" 395 | fi 396 | if [ "$haveSslConfig" ] && [ -f "$combinedSsl" ]; then 397 | # More ENV vars for make clustering happiness 398 | # we don't handle clustering in this script, but these args should ensure 399 | # clustered SSL-enabled members will talk nicely 400 | export ERL_SSL_PATH="$(erl -eval 'io:format("~p", [code:lib_dir(ssl, ebin)]),halt().' -noshell)" 401 | sslErlArgs="-pa $ERL_SSL_PATH -proto_dist inet_tls -ssl_dist_opt server_certfile $combinedSsl -ssl_dist_opt server_secure_renegotiate true client_secure_renegotiate true" 402 | export RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="${RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS:-} $sslErlArgs" 403 | export RABBITMQ_CTL_ERL_ARGS="${RABBITMQ_CTL_ERL_ARGS:-} $sslErlArgs" 404 | fi 405 | 406 | exec "$@" 407 | -------------------------------------------------------------------------------- /entrypoint/original.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # usage: file_env VAR [DEFAULT] 5 | # ie: file_env 'XYZ_DB_PASSWORD' 'example' 6 | # (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of 7 | # "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) 8 | file_env() { 9 | local var="$1" 10 | local fileVar="${var}_FILE" 11 | local def="${2:-}" 12 | if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then 13 | echo >&2 "error: both $var and $fileVar are set (but are exclusive)" 14 | exit 1 15 | fi 16 | local val="$def" 17 | if [ "${!var:-}" ]; then 18 | val="${!var}" 19 | elif [ "${!fileVar:-}" ]; then 20 | val="$(< "${!fileVar}")" 21 | fi 22 | export "$var"="$val" 23 | unset "$fileVar" 24 | } 25 | 26 | # allow the container to be started with `--user` 27 | if [[ "$1" == rabbitmq* ]] && [ "$(id -u)" = '0' ]; then 28 | if [ "$1" = 'rabbitmq-server' ]; then 29 | chown -R rabbitmq /var/lib/rabbitmq 30 | fi 31 | exec su-exec rabbitmq "$BASH_SOURCE" "$@" 32 | fi 33 | 34 | # backwards compatibility for old environment variables 35 | : "${RABBITMQ_SSL_CERTFILE:=${RABBITMQ_SSL_CERT_FILE:-}}" 36 | : "${RABBITMQ_SSL_KEYFILE:=${RABBITMQ_SSL_KEY_FILE:-}}" 37 | : "${RABBITMQ_SSL_CACERTFILE:=${RABBITMQ_SSL_CA_FILE:-}}" 38 | 39 | # "management" SSL config should default to using the same certs 40 | : "${RABBITMQ_MANAGEMENT_SSL_CACERTFILE:=$RABBITMQ_SSL_CACERTFILE}" 41 | : "${RABBITMQ_MANAGEMENT_SSL_CERTFILE:=$RABBITMQ_SSL_CERTFILE}" 42 | : "${RABBITMQ_MANAGEMENT_SSL_KEYFILE:=$RABBITMQ_SSL_KEYFILE}" 43 | 44 | # Allowed env vars that will be read from mounted files (i.e. Docker Secrets): 45 | fileEnvKeys=( 46 | default_user 47 | default_pass 48 | ) 49 | 50 | # https://www.rabbitmq.com/configure.html 51 | sslConfigKeys=( 52 | cacertfile 53 | certfile 54 | depth 55 | fail_if_no_peer_cert 56 | keyfile 57 | verify 58 | ) 59 | managementConfigKeys=( 60 | "${sslConfigKeys[@]/#/ssl_}" 61 | ) 62 | rabbitConfigKeys=( 63 | default_pass 64 | default_user 65 | default_vhost 66 | hipe_compile 67 | vm_memory_high_watermark 68 | ) 69 | fileConfigKeys=( 70 | management_ssl_cacertfile 71 | management_ssl_certfile 72 | management_ssl_keyfile 73 | ssl_cacertfile 74 | ssl_certfile 75 | ssl_keyfile 76 | ) 77 | allConfigKeys=( 78 | "${managementConfigKeys[@]/#/management_}" 79 | "${rabbitConfigKeys[@]}" 80 | "${sslConfigKeys[@]/#/ssl_}" 81 | ) 82 | 83 | declare -A configDefaults=( 84 | [management_ssl_fail_if_no_peer_cert]='false' 85 | [management_ssl_verify]='verify_none' 86 | 87 | [ssl_fail_if_no_peer_cert]='true' 88 | [ssl_verify]='verify_peer' 89 | ) 90 | 91 | haveConfig= 92 | haveSslConfig= 93 | haveManagementSslConfig= 94 | for fileEnvKey in "${fileEnvKeys[@]}"; do file_env "RABBITMQ_${fileEnvKey^^}"; done 95 | for conf in "${allConfigKeys[@]}"; do 96 | var="RABBITMQ_${conf^^}" 97 | val="${!var:-}" 98 | if [ "$val" ]; then 99 | if [ "${configDefaults[$conf]:-}" ] && [ "${configDefaults[$conf]}" = "$val" ]; then 100 | # if the value set is the same as the default, treat it as if it isn't set 101 | continue 102 | fi 103 | haveConfig=1 104 | case "$conf" in 105 | ssl_*) haveSslConfig=1 ;; 106 | management_ssl_*) haveManagementSslConfig=1 ;; 107 | esac 108 | fi 109 | done 110 | if [ "$haveSslConfig" ]; then 111 | missing=() 112 | for sslConf in cacertfile certfile keyfile; do 113 | var="RABBITMQ_SSL_${sslConf^^}" 114 | val="${!var}" 115 | if [ -z "$val" ]; then 116 | missing+=( "$var" ) 117 | fi 118 | done 119 | if [ "${#missing[@]}" -gt 0 ]; then 120 | { 121 | echo 122 | echo 'error: SSL requested, but missing required configuration' 123 | for miss in "${missing[@]}"; do 124 | echo " - $miss" 125 | done 126 | echo 127 | } >&2 128 | exit 1 129 | fi 130 | fi 131 | missingFiles=() 132 | for conf in "${fileConfigKeys[@]}"; do 133 | var="RABBITMQ_${conf^^}" 134 | val="${!var}" 135 | if [ "$val" ] && [ ! -f "$val" ]; then 136 | missingFiles+=( "$val ($var)" ) 137 | fi 138 | done 139 | if [ "${#missingFiles[@]}" -gt 0 ]; then 140 | { 141 | echo 142 | echo 'error: files specified, but missing' 143 | for miss in "${missingFiles[@]}"; do 144 | echo " - $miss" 145 | done 146 | echo 147 | } >&2 148 | exit 1 149 | fi 150 | 151 | # set defaults for missing values (but only after we're done with all our checking so we don't throw any of that off) 152 | for conf in "${!configDefaults[@]}"; do 153 | default="${configDefaults[$conf]}" 154 | var="RABBITMQ_${conf^^}" 155 | [ -z "${!var:-}" ] || continue 156 | eval "export $var=\"\$default\"" 157 | done 158 | 159 | # If long & short hostnames are not the same, use long hostnames 160 | if [ "$(hostname)" != "$(hostname -s)" ]; then 161 | : "${RABBITMQ_USE_LONGNAME:=true}" 162 | fi 163 | 164 | if [ "${RABBITMQ_ERLANG_COOKIE:-}" ]; then 165 | cookieFile='/var/lib/rabbitmq/.erlang.cookie' 166 | if [ -e "$cookieFile" ]; then 167 | if [ "$(cat "$cookieFile" 2>/dev/null)" != "$RABBITMQ_ERLANG_COOKIE" ]; then 168 | echo >&2 169 | echo >&2 "warning: $cookieFile contents do not match RABBITMQ_ERLANG_COOKIE" 170 | echo >&2 171 | fi 172 | else 173 | echo "$RABBITMQ_ERLANG_COOKIE" > "$cookieFile" 174 | chmod 600 "$cookieFile" 175 | fi 176 | fi 177 | 178 | # prints "$2$1$3$1...$N" 179 | join() { 180 | local sep="$1"; shift 181 | local out; printf -v out "${sep//%/%%}%s" "$@" 182 | echo "${out#$sep}" 183 | } 184 | indent() { 185 | if [ "$#" -gt 0 ]; then 186 | echo "$@" 187 | else 188 | cat 189 | fi | sed 's/^/\t/g' 190 | } 191 | rabbit_array() { 192 | echo -n '[' 193 | case "$#" in 194 | 0) echo -n ' ' ;; 195 | 1) echo -n " $1 " ;; 196 | *) 197 | local vals="$(join $',\n' "$@")" 198 | echo 199 | indent "$vals" 200 | esac 201 | echo -n ']' 202 | } 203 | rabbit_env_config() { 204 | local prefix="$1"; shift 205 | 206 | local ret=() 207 | local conf 208 | for conf; do 209 | local var="rabbitmq${prefix:+_$prefix}_$conf" 210 | var="${var^^}" 211 | 212 | local val="${!var:-}" 213 | 214 | local rawVal= 215 | case "$conf" in 216 | verify|fail_if_no_peer_cert|depth) 217 | [ "$val" ] || continue 218 | rawVal="$val" 219 | ;; 220 | 221 | hipe_compile) 222 | [ "$val" ] && rawVal='true' || rawVal='false' 223 | ;; 224 | 225 | cacertfile|certfile|keyfile) 226 | [ "$val" ] || continue 227 | rawVal='"'"$val"'"' 228 | ;; 229 | 230 | *) 231 | [ "$val" ] || continue 232 | rawVal='<<"'"$val"'">>' 233 | ;; 234 | esac 235 | [ "$rawVal" ] || continue 236 | 237 | ret+=( "{ $conf, $rawVal }" ) 238 | done 239 | 240 | join $'\n' "${ret[@]}" 241 | } 242 | 243 | shouldWriteConfig="$haveConfig" 244 | if [ ! -f /etc/rabbitmq/rabbitmq.config ]; then 245 | shouldWriteConfig=1 246 | fi 247 | 248 | if [ "$1" = 'rabbitmq-server' ] && [ "$shouldWriteConfig" ]; then 249 | fullConfig=() 250 | 251 | rabbitConfig=( 252 | "{ loopback_users, $(rabbit_array) }" 253 | ) 254 | 255 | # determine whether to set "vm_memory_high_watermark" (based on cgroups) 256 | memTotalKb= 257 | if [ -r /proc/meminfo ]; then 258 | memTotalKb="$(awk -F ':? +' '$1 == "MemTotal" { print $2; exit }' /proc/meminfo)" 259 | fi 260 | memLimitB= 261 | if [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then 262 | # "18446744073709551615" is a valid value for "memory.limit_in_bytes", which is too big for Bash math to handle 263 | # "$(( 18446744073709551615 / 1024 ))" = 0; "$(( 18446744073709551615 * 40 / 100 ))" = 0 264 | memLimitB="$(awk -v totKb="$memTotalKb" '{ 265 | limB = $0; 266 | limKb = limB / 1024; 267 | if (!totKb || limKb < totKb) { 268 | printf "%.0f\n", limB; 269 | } 270 | }' /sys/fs/cgroup/memory/memory.limit_in_bytes)" 271 | fi 272 | if [ -n "$memTotalKb" ] || [ -n "$memLimitB" ]; then 273 | # https://github.com/docker-library/rabbitmq/pull/105#issuecomment-242165822 274 | vmMemoryHighWatermark= 275 | if [ "${RABBITMQ_VM_MEMORY_HIGH_WATERMARK:-}" ]; then 276 | vmMemoryHighWatermark="$( 277 | awk -v lim="$memLimitB" ' 278 | /^[0-9]*[.][0-9]+$|^[0-9]+([.][0-9]+)?%$/ { 279 | perc = $0; 280 | if (perc ~ /%$/) { 281 | gsub(/%$/, "", perc); 282 | perc = perc / 100; 283 | } 284 | if (perc > 1.0 || perc <= 0.0) { 285 | printf "error: invalid percentage for vm_memory_high_watermark: %s (must be > 0%%, <= 100%%)\n", $0 > "/dev/stderr"; 286 | exit 1; 287 | } 288 | if (lim) { 289 | printf "{ absolute, %d }\n", lim * perc; 290 | } else { 291 | printf "%0.03f\n", perc; 292 | } 293 | next; 294 | } 295 | /^[0-9]+$/ { 296 | printf "{ absolute, %s }\n", $0; 297 | next; 298 | } 299 | /^[0-9]+([.][0-9]+)?[a-zA-Z]+$/ { 300 | printf "{ absolute, \"%s\" }\n", $0; 301 | next; 302 | } 303 | { 304 | printf "error: unexpected input for vm_memory_high_watermark: %s\n", $0; 305 | exit 1; 306 | } 307 | ' <(echo "$RABBITMQ_VM_MEMORY_HIGH_WATERMARK") 308 | )" 309 | elif [ -n "$memLimitB" ]; then 310 | # if there is a cgroup limit, default to 40% of _that_ (as recommended by upstream) 311 | vmMemoryHighWatermark="{ absolute, $(awk -v lim="$memLimitB" 'BEGIN { printf "%.0f\n", lim * 0.4; exit }') }" 312 | # otherwise let the default behavior win (40% of the total available) 313 | fi 314 | if [ "$vmMemoryHighWatermark" ]; then 315 | # https://www.rabbitmq.com/memory.html#memsup-usage 316 | rabbitConfig+=( "{ vm_memory_high_watermark, $vmMemoryHighWatermark }" ) 317 | fi 318 | elif [ "${RABBITMQ_VM_MEMORY_HIGH_WATERMARK:-}" ]; then 319 | echo >&2 'warning: RABBITMQ_VM_MEMORY_HIGH_WATERMARK was specified, but current system memory or cgroup memory limit cannot be determined' 320 | echo >&2 ' (so "vm_memory_high_watermark" will not be set)' 321 | fi 322 | 323 | if [ "$haveSslConfig" ]; then 324 | IFS=$'\n' 325 | rabbitSslOptions=( $(rabbit_env_config 'ssl' "${sslConfigKeys[@]}") ) 326 | unset IFS 327 | 328 | rabbitConfig+=( 329 | "{ tcp_listeners, $(rabbit_array) }" 330 | "{ ssl_listeners, $(rabbit_array 5671) }" 331 | "{ ssl_options, $(rabbit_array "${rabbitSslOptions[@]}") }" 332 | ) 333 | else 334 | rabbitConfig+=( 335 | "{ tcp_listeners, $(rabbit_array 5672) }" 336 | "{ ssl_listeners, $(rabbit_array) }" 337 | ) 338 | fi 339 | 340 | IFS=$'\n' 341 | rabbitConfig+=( $(rabbit_env_config '' "${rabbitConfigKeys[@]}") ) 342 | unset IFS 343 | 344 | fullConfig+=( "{ rabbit, $(rabbit_array "${rabbitConfig[@]}") }" ) 345 | 346 | # if management plugin is installed, generate config for it 347 | # https://www.rabbitmq.com/management.html#configuration 348 | if [ "$(rabbitmq-plugins list -m -e rabbitmq_management)" ]; then 349 | rabbitManagementConfig=() 350 | 351 | if [ "$haveManagementSslConfig" ]; then 352 | IFS=$'\n' 353 | rabbitManagementSslOptions=( $(rabbit_env_config 'management_ssl' "${sslConfigKeys[@]}") ) 354 | unset IFS 355 | 356 | rabbitManagementListenerConfig+=( 357 | '{ port, 15671 }' 358 | '{ ssl, true }' 359 | "{ ssl_opts, $(rabbit_array "${rabbitManagementSslOptions[@]}") }" 360 | ) 361 | else 362 | rabbitManagementListenerConfig+=( 363 | '{ port, 15672 }' 364 | '{ ssl, false }' 365 | ) 366 | fi 367 | rabbitManagementConfig+=( 368 | "{ listener, $(rabbit_array "${rabbitManagementListenerConfig[@]}") }" 369 | ) 370 | 371 | # if definitions file exists, then load it 372 | # https://www.rabbitmq.com/management.html#load-definitions 373 | managementDefinitionsFile='/etc/rabbitmq/definitions.json' 374 | if [ -f "${managementDefinitionsFile}" ]; then 375 | # see also https://github.com/docker-library/rabbitmq/pull/112#issuecomment-271485550 376 | rabbitManagementConfig+=( 377 | "{ load_definitions, \"$managementDefinitionsFile\" }" 378 | ) 379 | fi 380 | 381 | fullConfig+=( 382 | "{ rabbitmq_management, $(rabbit_array "${rabbitManagementConfig[@]}") }" 383 | ) 384 | fi 385 | 386 | echo "$(rabbit_array "${fullConfig[@]}")." > /etc/rabbitmq/rabbitmq.config 387 | fi 388 | 389 | combinedSsl='/tmp/combined.pem' 390 | if [ "$haveSslConfig" ] && [[ "$1" == rabbitmq* ]] && [ ! -f "$combinedSsl" ]; then 391 | # Create combined cert 392 | cat "$RABBITMQ_SSL_CERTFILE" "$RABBITMQ_SSL_KEYFILE" > "$combinedSsl" 393 | chmod 0400 "$combinedSsl" 394 | fi 395 | if [ "$haveSslConfig" ] && [ -f "$combinedSsl" ]; then 396 | # More ENV vars for make clustering happiness 397 | # we don't handle clustering in this script, but these args should ensure 398 | # clustered SSL-enabled members will talk nicely 399 | export ERL_SSL_PATH="$(erl -eval 'io:format("~p", [code:lib_dir(ssl, ebin)]),halt().' -noshell)" 400 | sslErlArgs="-pa $ERL_SSL_PATH -proto_dist inet_tls -ssl_dist_opt server_certfile $combinedSsl -ssl_dist_opt server_secure_renegotiate true client_secure_renegotiate true" 401 | export RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="${RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS:-} $sslErlArgs" 402 | export RABBITMQ_CTL_ERL_ARGS="${RABBITMQ_CTL_ERL_ARGS:-} $sslErlArgs" 403 | fi 404 | 405 | exec "$@" 406 | -------------------------------------------------------------------------------- /examples/kubernetes-rbac.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | kind: ClusterRole 4 | metadata: 5 | name: rabbitmq 6 | rules: 7 | - apiGroups: 8 | - apps 9 | resources: 10 | - statefulsets 11 | verbs: 12 | - get 13 | - list 14 | --- 15 | apiVersion: v1 16 | kind: ServiceAccount 17 | metadata: 18 | name: rabbitmq 19 | namespace: default 20 | --- 21 | kind: ClusterRoleBinding 22 | apiVersion: rbac.authorization.k8s.io/v1beta1 23 | metadata: 24 | name: rabbitmq 25 | roleRef: 26 | kind: ClusterRole 27 | name: rabbitmq 28 | apiGroup: rbac.authorization.k8s.io 29 | subjects: 30 | - kind: ServiceAccount 31 | name: rabbitmq 32 | namespace: default 33 | --- 34 | kind: Service 35 | apiVersion: v1 36 | metadata: 37 | name: rabbitmq 38 | spec: 39 | clusterIP: None # We need a headless service to allow the pods to discover each 40 | ports: # other during autodiscover phase for cluster creation. 41 | - name: http # A ClusterIP will prevent resolving dns requests for other pods 42 | protocol: TCP # under the same service. 43 | port: 15672 44 | targetPort: 15672 45 | - name: amqp 46 | protocol: TCP 47 | port: 5672 48 | targetPort: 5672 49 | selector: 50 | app: rabbitmq 51 | --- 52 | apiVersion: apps/v1beta1 53 | kind: StatefulSet 54 | metadata: 55 | name: rabbitmq 56 | spec: 57 | serviceName: rabbitmq 58 | replicas: 3 59 | updateStrategy: 60 | type: RollingUpdate 61 | template: 62 | metadata: 63 | labels: 64 | app: rabbitmq 65 | spec: 66 | serviceAccountName: rabbitmq 67 | terminationGracePeriodSeconds: 10 68 | imagePullSecrets: 69 | - name: hub.docker.com 70 | containers: 71 | - name: rabbitmq 72 | image: foxylion/rabbitmq:3.6-autocluster 73 | imagePullPolicy: Always 74 | ports: 75 | - name: http 76 | protocol: TCP 77 | containerPort: 15672 78 | - name: amqp 79 | protocol: TCP 80 | containerPort: 5672 81 | readinessProbe: # This readiness probe will delay the startup of the nex pod for 30 seconds. It ensures 82 | exec: # the node is most probably in a state that let the next node connect to this one. 83 | command: ["true"] # Doing a real liveness or readiness probe will result in a stuck statfult set if the 84 | initialDelaySeconds: 30 # cluster is in a "pause_minority" state. In this state all health checks provided 85 | timeoutSeconds: 5 # by rabbitmq will fail, but the node should _not_ be restarted. 86 | env: 87 | - name: MY_POD_NAME 88 | valueFrom: 89 | fieldRef: 90 | fieldPath: metadata.name 91 | - name: RABBITMQ_NODENAME 92 | value: "rabbit@$(MY_POD_NAME).rabbitmq.default.svc.cluster.local" 93 | - name: RABBITMQ_USE_LONGNAME 94 | value: "true" 95 | - name: RABBITMQ_VM_MEMORY_HIGH_WATERMARK 96 | value: "0.49" 97 | - name: RABBITMQ_CLUSTER_PARTITION_HANDLING 98 | value: pause_minority 99 | - name: RABBITMQ_ERLANG_COOKIE 100 | value: erlang-cookie-placeholder 101 | - name: RABBITMQ_DEFAULT_USER 102 | value: admin 103 | - name: RABBITMQ_DEFAULT_PASS 104 | value: admin 105 | - name: AUTOCLUSTER_TYPE 106 | value: "k8s" 107 | - name: AUTOCLUSTER_DELAY 108 | value: "10" 109 | - name: AUTOCLUSTER_FAILURE 110 | value: 'stop' 111 | - name: AUTOCLUSTER_LOG_LEVEL 112 | value: debug 113 | - name: CLEANUP_WARN_ONLY 114 | value: "false" 115 | - name: K8S_ADDRESS_TYPE 116 | value: "hostname" 117 | - name: K8S_HOSTNAME_SUFFIX 118 | value: ".rabbitmq.default.svc.cluster.local" 119 | volumeMounts: 120 | - name: rabbitmq-persistent-storage 121 | mountPath: /var/lib/rabbitmq 122 | volumeClaimTemplates: 123 | - metadata: 124 | name: rabbitmq-persistent-storage 125 | annotations: 126 | volume.beta.kubernetes.io/storage-class: standard 127 | spec: 128 | accessModes: ["ReadWriteOnce"] 129 | resources: 130 | requests: 131 | storage: 10Gi 132 | -------------------------------------------------------------------------------- /examples/kubernetes.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: rabbitmq 5 | spec: 6 | clusterIP: None # We need a headless service to allow the pods to discover each 7 | ports: # other during autodiscover phase for cluster creation. 8 | - name: http # A ClusterIP will prevent resolving dns requests for other pods 9 | protocol: TCP # under the same service. 10 | port: 15672 11 | targetPort: 15672 12 | - name: amqp 13 | protocol: TCP 14 | port: 5672 15 | targetPort: 5672 16 | selector: 17 | app: rabbitmq 18 | --- 19 | apiVersion: apps/v1beta1 20 | kind: StatefulSet 21 | metadata: 22 | name: rabbitmq 23 | spec: 24 | serviceName: rabbitmq 25 | replicas: 3 26 | updateStrategy: 27 | type: RollingUpdate 28 | template: 29 | metadata: 30 | labels: 31 | app: rabbitmq 32 | spec: 33 | terminationGracePeriodSeconds: 10 34 | imagePullSecrets: 35 | - name: hub.docker.com 36 | containers: 37 | - name: rabbitmq 38 | image: foxylion/rabbitmq:3.6-autocluster 39 | imagePullPolicy: Always 40 | ports: 41 | - name: http 42 | protocol: TCP 43 | containerPort: 15672 44 | - name: amqp 45 | protocol: TCP 46 | containerPort: 5672 47 | readinessProbe: # This readiness probe will delay the startup of the nex pod for 30 seconds. It ensures 48 | exec: # the node is most probably in a state that let the next node connect to this one. 49 | command: ["true"] # Doing a real liveness or readiness probe will result in a stuck statfult set if the 50 | initialDelaySeconds: 30 # cluster is in a "pause_minority" state. In this state all health checks provided 51 | timeoutSeconds: 5 # by rabbitmq will fail, but the node should _not_ be restarted. 52 | env: 53 | - name: MY_POD_NAME 54 | valueFrom: 55 | fieldRef: 56 | fieldPath: metadata.name 57 | - name: RABBITMQ_NODENAME 58 | value: "rabbit@$(MY_POD_NAME).rabbitmq.default.svc.cluster.local" 59 | - name: RABBITMQ_USE_LONGNAME 60 | value: "true" 61 | - name: RABBITMQ_VM_MEMORY_HIGH_WATERMARK 62 | value: "0.49" 63 | - name: RABBITMQ_CLUSTER_PARTITION_HANDLING 64 | value: pause_minority 65 | - name: RABBITMQ_ERLANG_COOKIE 66 | value: erlang-cookie-placeholder 67 | - name: RABBITMQ_DEFAULT_USER 68 | value: admin 69 | - name: RABBITMQ_DEFAULT_PASS 70 | value: admin 71 | - name: AUTOCLUSTER_TYPE 72 | value: "k8s" 73 | - name: AUTOCLUSTER_DELAY 74 | value: "10" 75 | - name: AUTOCLUSTER_FAILURE 76 | value: 'stop' 77 | - name: AUTOCLUSTER_LOG_LEVEL 78 | value: debug 79 | - name: CLEANUP_WARN_ONLY 80 | value: "false" 81 | - name: K8S_ADDRESS_TYPE 82 | value: "hostname" 83 | - name: K8S_HOSTNAME_SUFFIX 84 | value: ".rabbitmq.default.svc.cluster.local" 85 | volumeMounts: 86 | - name: rabbitmq-persistent-storage 87 | mountPath: /var/lib/rabbitmq 88 | volumeClaimTemplates: 89 | - metadata: 90 | name: rabbitmq-persistent-storage 91 | annotations: 92 | volume.beta.kubernetes.io/storage-class: standard 93 | spec: 94 | accessModes: ["ReadWriteOnce"] 95 | resources: 96 | requests: 97 | storage: 10Gi 98 | --------------------------------------------------------------------------------