scripts-configmap.yaml 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. {{- /*
  2. Copyright VMware, Inc.
  3. SPDX-License-Identifier: APACHE-2.0
  4. */}}
  5. {{- $releaseNamespace := include "common.names.namespace" . }}
  6. {{- $fullname := include "common.names.fullname" . }}
  7. {{- $clusterDomain := .Values.clusterDomain }}
  8. apiVersion: v1
  9. kind: ConfigMap
  10. metadata:
  11. name: {{ printf "%s-scripts" $fullname }}
  12. namespace: {{ $releaseNamespace | quote }}
  13. labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
  14. {{- if .Values.commonAnnotations }}
  15. annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
  16. {{- end }}
  17. data:
  18. {{- if .Values.externalAccess.autoDiscovery.enabled }}
  19. auto-discovery.sh: |-
  20. #!/bin/bash
  21. SVC_NAME="${MY_POD_NAME}-external"
  22. AUTODISCOVERY_SERVICE_TYPE="${AUTODISCOVERY_SERVICE_TYPE:-}"
  23. # Auxiliary functions
  24. retry_while() {
  25. local -r cmd="${1:?cmd is missing}"
  26. local -r retries="${2:-12}"
  27. local -r sleep_time="${3:-5}"
  28. local return_value=1
  29. read -r -a command <<< "$cmd"
  30. for ((i = 1 ; i <= retries ; i+=1 )); do
  31. "${command[@]}" && return_value=0 && break
  32. sleep "$sleep_time"
  33. done
  34. return $return_value
  35. }
  36. k8s_svc_lb_ip() {
  37. local namespace=${1:?namespace is missing}
  38. local service=${2:?service is missing}
  39. local service_ip=$(kubectl get svc "$service" -n "$namespace" -o jsonpath="{.status.loadBalancer.ingress[0].ip}")
  40. local service_hostname=$(kubectl get svc "$service" -n "$namespace" -o jsonpath="{.status.loadBalancer.ingress[0].hostname}")
  41. if [[ -n ${service_ip} ]]; then
  42. echo "${service_ip}"
  43. else
  44. echo "${service_hostname}"
  45. fi
  46. }
  47. k8s_svc_lb_ip_ready() {
  48. local namespace=${1:?namespace is missing}
  49. local service=${2:?service is missing}
  50. [[ -n "$(k8s_svc_lb_ip "$namespace" "$service")" ]]
  51. }
  52. k8s_svc_node_port() {
  53. local namespace=${1:?namespace is missing}
  54. local service=${2:?service is missing}
  55. local index=${3:-0}
  56. local node_port="$(kubectl get svc "$service" -n "$namespace" -o jsonpath="{.spec.ports[$index].nodePort}")"
  57. echo "$node_port"
  58. }
  59. if [[ "$AUTODISCOVERY_SERVICE_TYPE" = "LoadBalancer" ]]; then
  60. # Wait until LoadBalancer IP is ready
  61. retry_while "k8s_svc_lb_ip_ready {{ $releaseNamespace }} $SVC_NAME" || exit 1
  62. # Obtain LoadBalancer external IP
  63. k8s_svc_lb_ip "{{ $releaseNamespace }}" "$SVC_NAME" | tee "/shared/external-host.txt"
  64. elif [[ "$AUTODISCOVERY_SERVICE_TYPE" = "NodePort" ]]; then
  65. k8s_svc_node_port "{{ $releaseNamespace }}" "$SVC_NAME" | tee "/shared/external-port.txt"
  66. else
  67. echo "Unsupported autodiscovery service type: '$AUTODISCOVERY_SERVICE_TYPE'"
  68. exit 1
  69. fi
  70. {{- end }}
  71. kafka-init.sh: |-
  72. #!/bin/bash
  73. set -o errexit
  74. set -o nounset
  75. set -o pipefail
  76. error(){
  77. local message="${1:?missing message}"
  78. echo "ERROR: ${message}"
  79. exit 1
  80. }
  81. retry_while() {
  82. local -r cmd="${1:?cmd is missing}"
  83. local -r retries="${2:-12}"
  84. local -r sleep_time="${3:-5}"
  85. local return_value=1
  86. read -r -a command <<< "$cmd"
  87. for ((i = 1 ; i <= retries ; i+=1 )); do
  88. "${command[@]}" && return_value=0 && break
  89. sleep "$sleep_time"
  90. done
  91. return $return_value
  92. }
  93. replace_in_file() {
  94. local filename="${1:?filename is required}"
  95. local match_regex="${2:?match regex is required}"
  96. local substitute_regex="${3:?substitute regex is required}"
  97. local posix_regex=${4:-true}
  98. local result
  99. # We should avoid using 'sed in-place' substitutions
  100. # 1) They are not compatible with files mounted from ConfigMap(s)
  101. # 2) We found incompatibility issues with Debian10 and "in-place" substitutions
  102. local -r del=$'\001' # Use a non-printable character as a 'sed' delimiter to avoid issues
  103. if [[ $posix_regex = true ]]; then
  104. result="$(sed -E "s${del}${match_regex}${del}${substitute_regex}${del}g" "$filename")"
  105. else
  106. result="$(sed "s${del}${match_regex}${del}${substitute_regex}${del}g" "$filename")"
  107. fi
  108. echo "$result" > "$filename"
  109. }
  110. kafka_conf_set() {
  111. local file="${1:?missing file}"
  112. local key="${2:?missing key}"
  113. local value="${3:?missing value}"
  114. # Check if the value was set before
  115. if grep -q "^[#\\s]*$key\s*=.*" "$file"; then
  116. # Update the existing key
  117. replace_in_file "$file" "^[#\\s]*${key}\s*=.*" "${key}=${value}" false
  118. else
  119. # Add a new key
  120. printf '\n%s=%s' "$key" "$value" >>"$file"
  121. fi
  122. }
  123. replace_placeholder() {
  124. local placeholder="${1:?missing placeholder value}"
  125. local password="${2:?missing password value}"
  126. sed -i "s/$placeholder/$password/g" "$KAFKA_CONFIG_FILE"
  127. }
  128. append_file_to_kafka_conf() {
  129. local file="${1:?missing source file}"
  130. local conf="${2:?missing kafka conf file}"
  131. cat "$1" >> "$2"
  132. }
  133. configure_external_access() {
  134. # Configure external hostname
  135. if [[ -f "/shared/external-host.txt" ]]; then
  136. host=$(cat "/shared/external-host.txt")
  137. elif [[ -n "${EXTERNAL_ACCESS_HOST:-}" ]]; then
  138. host="$EXTERNAL_ACCESS_HOST"
  139. elif [[ -n "${EXTERNAL_ACCESS_HOSTS_LIST:-}" ]]; then
  140. read -r -a hosts <<<"$(tr ',' ' ' <<<"${EXTERNAL_ACCESS_HOSTS_LIST}")"
  141. host="${hosts[$POD_ID]}"
  142. elif [[ "$EXTERNAL_ACCESS_HOST_USE_PUBLIC_IP" =~ ^(yes|true)$ ]]; then
  143. host=$(curl -s https://ipinfo.io/ip)
  144. else
  145. error "External access hostname not provided"
  146. fi
  147. # Configure external port
  148. if [[ -f "/shared/external-port.txt" ]]; then
  149. port=$(cat "/shared/external-port.txt")
  150. elif [[ -n "${EXTERNAL_ACCESS_PORT:-}" ]]; then
  151. if [[ "${EXTERNAL_ACCESS_PORT_AUTOINCREMENT:-}" =~ ^(yes|true)$ ]]; then
  152. port="$((EXTERNAL_ACCESS_PORT + POD_ID))"
  153. else
  154. port="$EXTERNAL_ACCESS_PORT"
  155. fi
  156. elif [[ -n "${EXTERNAL_ACCESS_PORTS_LIST:-}" ]]; then
  157. read -r -a ports <<<"$(tr ',' ' ' <<<"${EXTERNAL_ACCESS_PORTS_LIST}")"
  158. port="${ports[$POD_ID]}"
  159. else
  160. error "External access port not provided"
  161. fi
  162. # Configure Kafka advertised listeners
  163. sed -i -E "s|^(advertised\.listeners=\S+)$|\1,{{ upper .Values.listeners.external.name }}://${host}:${port}|" "$KAFKA_CONFIG_FILE"
  164. }
  165. {{- if (include "kafka.sslEnabled" .) }}
  166. configure_kafka_tls() {
  167. # Remove previously existing keystores and certificates, if any
  168. rm -f /certs/kafka.keystore.jks /certs/kafka.truststore.jks
  169. rm -f /certs/tls.crt /certs/tls.key /certs/ca.crt
  170. find /certs -name "xx*" -exec rm {} \;
  171. if [[ "${KAFKA_TLS_TYPE}" = "PEM" ]]; then
  172. # Copy PEM certificate and key
  173. if [[ -f "/mounted-certs/kafka-${POD_ROLE}-${POD_ID}.crt" && "/mounted-certs/kafka-${POD_ROLE}-${POD_ID}.key" ]]; then
  174. cp "/mounted-certs/kafka-${POD_ROLE}-${POD_ID}.crt" /certs/tls.crt
  175. # Copy the PEM key ensuring the key used PEM format with PKCS#8
  176. openssl pkcs8 -topk8 -nocrypt -in "/mounted-certs/kafka-${POD_ROLE}-${POD_ID}.key" > /certs/tls.key
  177. elif [[ -f /mounted-certs/kafka.crt && -f /mounted-certs/kafka.key ]]; then
  178. cp "/mounted-certs/kafka.crt" /certs/tls.crt
  179. # Copy the PEM key ensuring the key used PEM format with PKCS#8
  180. openssl pkcs8 -topk8 -nocrypt -in "/mounted-certs/kafka.key" > /certs/tls.key
  181. elif [[ -f /mounted-certs/tls.crt && -f /mounted-certs/tls.key ]]; then
  182. cp "/mounted-certs/tls.crt" /certs/tls.crt
  183. # Copy the PEM key ensuring the key used PEM format with PKCS#8
  184. openssl pkcs8 -topk8 -nocrypt -in "/mounted-certs/tls.key" > /certs/tls.key
  185. else
  186. error "PEM key and cert files not found"
  187. fi
  188. {{- if not .Values.tls.pemChainIncluded }}
  189. # Copy CA certificate
  190. if [[ -f /mounted-certs/kafka-ca.crt ]]; then
  191. cp /mounted-certs/kafka-ca.crt /certs/ca.crt
  192. elif [[ -f /mounted-certs/ca.crt ]]; then
  193. cp /mounted-certs/ca.crt /certs/ca.crt
  194. else
  195. error "CA certificate file not found"
  196. fi
  197. {{- else }}
  198. # CA certificates are also included in the same certificate
  199. # All public certs will be included in the truststore
  200. cp /certs/tls.crt /certs/ca.crt
  201. {{- end }}
  202. # Create JKS keystore from PEM cert and key
  203. openssl pkcs12 -export -in "/certs/tls.crt" \
  204. -passout pass:"${KAFKA_TLS_KEYSTORE_PASSWORD}" \
  205. -inkey "/certs/tls.key" \
  206. -out "/certs/kafka.keystore.p12"
  207. keytool -importkeystore -srckeystore "/certs/kafka.keystore.p12" \
  208. -srcstoretype PKCS12 \
  209. -srcstorepass "${KAFKA_TLS_KEYSTORE_PASSWORD}" \
  210. -deststorepass "${KAFKA_TLS_KEYSTORE_PASSWORD}" \
  211. -destkeystore "/certs/kafka.keystore.jks" \
  212. -noprompt
  213. # Create JKS truststore from CA cert
  214. keytool -keystore /certs/kafka.truststore.jks -alias CARoot -import -file /certs/ca.crt -storepass "${KAFKA_TLS_TRUSTSTORE_PASSWORD}" -noprompt
  215. # Remove extra files
  216. rm -f "/certs/kafka.keystore.p12" "/certs/tls.crt" "/certs/tls.key" "/certs/ca.crt"
  217. elif [[ "${KAFKA_TLS_TYPE}" = "JKS" ]]; then
  218. if [[ -f "/mounted-certs/kafka-${POD_ROLE}-${POD_ID}.keystore.jks" ]]; then
  219. cp "/mounted-certs/kafka-${POD_ROLE}-${POD_ID}.keystore.jks" /certs/kafka.keystore.jks
  220. elif [[ -f {{ printf "/mounted-certs/%s" ( default "kafka.keystore.jks" .Values.tls.jksKeystoreKey) | quote }} ]]; then
  221. cp {{ printf "/mounted-certs/%s" ( default "kafka.keystore.jks" .Values.tls.jksKeystoreKey) | quote }} /certs/kafka.keystore.jks
  222. else
  223. error "Keystore file not found"
  224. fi
  225. if [[ -f {{ printf "/mounted-certs/%s" ( default "kafka.truststore.jks" .Values.tls.jksTruststoreKey) | quote }} ]]; then
  226. cp {{ printf "/mounted-certs/%s" ( default "kafka.truststore.jks" .Values.tls.jksTruststoreKey) | quote }} /certs/kafka.truststore.jks
  227. else
  228. error "Truststore file not found"
  229. fi
  230. else
  231. error "Invalid type ${KAFKA_TLS_TYPE}"
  232. fi
  233. # Configure TLS password settings in Kafka configuration
  234. [[ -n "${KAFKA_TLS_KEYSTORE_PASSWORD:-}" ]] && kafka_conf_set "$KAFKA_CONFIG_FILE" "ssl.keystore.password" "$KAFKA_TLS_KEYSTORE_PASSWORD"
  235. [[ -n "${KAFKA_TLS_TRUSTSTORE_PASSWORD:-}" ]] && kafka_conf_set "$KAFKA_CONFIG_FILE" "ssl.truststore.password" "$KAFKA_TLS_TRUSTSTORE_PASSWORD"
  236. [[ -n "${KAFKA_TLS_PEM_KEY_PASSWORD:-}" ]] && kafka_conf_set "$KAFKA_CONFIG_FILE" "ssl.key.password" "$KAFKA_TLS_PEM_KEY_PASSWORD"
  237. # Avoid errors caused by previous checks
  238. true
  239. }
  240. {{- end }}
  241. {{- if and .Values.tls.zookeeper.enabled .Values.tls.zookeeper.existingSecret }}
  242. configure_zookeeper_tls() {
  243. # Remove previously existing keystores
  244. rm -f /certs/zookeeper.keystore.jks /certs/zookeeper.truststore.jks
  245. ZOOKEEPER_TRUSTSTORE={{ printf "/zookeeper-certs/%s" .Values.tls.zookeeper.existingSecretTruststoreKey | quote }}
  246. ZOOKEEPER_KEYSTORE={{ printf "/zookeeper-certs/%s" .Values.tls.zookeeper.existingSecretKeystoreKey | quote }}
  247. if [[ -f "$ZOOKEEPER_KEYSTORE" ]]; then
  248. cp "$ZOOKEEPER_KEYSTORE" "/certs/zookeeper.keystore.jks"
  249. else
  250. error "Zookeeper keystore file not found"
  251. fi
  252. if [[ -f "$ZOOKEEPER_TRUSTSTORE" ]]; then
  253. cp "$ZOOKEEPER_TRUSTSTORE" "/certs/zookeeper.truststore.jks"
  254. else
  255. error "Zookeeper keystore file not found"
  256. fi
  257. [[ -n "${KAFKA_ZOOKEEPER_TLS_KEYSTORE_PASSWORD:-}" ]] && kafka_conf_set "$KAFKA_CONFIG_FILE" "zookeeper.ssl.keystore.password" "${KAFKA_ZOOKEEPER_TLS_KEYSTORE_PASSWORD}"
  258. [[ -n "${KAFKA_ZOOKEEPER_TLS_TRUSTSTORE_PASSWORD:-}" ]] && kafka_conf_set "$KAFKA_CONFIG_FILE" "zookeeper.ssl.truststore.password" "${KAFKA_ZOOKEEPER_TLS_TRUSTSTORE_PASSWORD}"
  259. # Avoid errors caused by previous checks
  260. true
  261. }
  262. {{- end }}
  263. {{- if (include "kafka.saslEnabled" .) }}
  264. configure_kafka_sasl() {
  265. # Replace placeholders with passwords
  266. {{- if regexFind "SASL" (upper .Values.listeners.interbroker.protocol) }}
  267. {{- if (include "kafka.saslUserPasswordsEnabled" .) }}
  268. replace_placeholder "interbroker-password-placeholder" "$KAFKA_INTER_BROKER_PASSWORD"
  269. {{- end }}
  270. {{- if (include "kafka.saslClientSecretsEnabled" .) }}
  271. replace_placeholder "interbroker-client-secret-placeholder" "$KAFKA_INTER_BROKER_CLIENT_SECRET"
  272. {{- end }}
  273. {{- end -}}
  274. {{- if and .Values.kraft.enabled (regexFind "SASL" (upper .Values.listeners.controller.protocol)) }}
  275. {{- if (include "kafka.saslUserPasswordsEnabled" .) }}
  276. replace_placeholder "controller-password-placeholder" "$KAFKA_CONTROLLER_PASSWORD"
  277. {{- end }}
  278. {{- if (include "kafka.saslClientSecretsEnabled" .) }}
  279. replace_placeholder "controller-client-secret-placeholder" "$KAFKA_CONTROLLER_CLIENT_SECRET"
  280. {{- end }}
  281. {{- end }}
  282. {{- if (include "kafka.client.saslEnabled" .)}}
  283. read -r -a passwords <<<"$(tr ',;' ' ' <<<"${KAFKA_CLIENT_PASSWORDS:-}")"
  284. for ((i = 0; i < ${#passwords[@]}; i++)); do
  285. replace_placeholder "password-placeholder-${i}" "${passwords[i]}"
  286. done
  287. {{- end }}
  288. {{- if .Values.sasl.zookeeper.user }}
  289. replace_placeholder "zookeeper-password-placeholder" "$KAFKA_ZOOKEEPER_PASSWORD"
  290. {{- end }}
  291. }
  292. {{- end }}
  293. {{- if .Values.externalAccess.autoDiscovery.enabled }}
  294. # Wait for autodiscovery to finish
  295. if [[ "${EXTERNAL_ACCESS_ENABLED:-false}" =~ ^(yes|true)$ ]]; then
  296. retry_while "test -f /shared/external-host.txt -o -f /shared/external-port.txt" || error "Timed out waiting for autodiscovery init-container"
  297. fi
  298. {{- end }}
  299. export KAFKA_CONFIG_FILE=/config/server.properties
  300. cp /configmaps/server.properties $KAFKA_CONFIG_FILE
  301. # Get pod ID and role, last and second last fields in the pod name respectively
  302. POD_ID=$(echo "$MY_POD_NAME" | rev | cut -d'-' -f 1 | rev)
  303. POD_ROLE=$(echo "$MY_POD_NAME" | rev | cut -d'-' -f 2 | rev)
  304. # Configure node.id and/or broker.id
  305. if [[ -f "/bitnami/kafka/data/meta.properties" ]]; then
  306. if grep -q "broker.id" /bitnami/kafka/data/meta.properties; then
  307. ID="$(grep "broker.id" /bitnami/kafka/data/meta.properties | awk -F '=' '{print $2}')"
  308. {{- if or (and .Values.kraft.enabled (not .Values.broker.zookeeperMigrationMode)) (and (not .Values.zookeeper.enabled) (not .Values.externalZookeeper.servers)) }}
  309. kafka_conf_set "$KAFKA_CONFIG_FILE" "node.id" "$ID"
  310. {{- else }}
  311. kafka_conf_set "$KAFKA_CONFIG_FILE" "broker.id" "$ID"
  312. {{- end }}
  313. else
  314. ID="$(grep "node.id" /bitnami/kafka/data/meta.properties | awk -F '=' '{print $2}')"
  315. kafka_conf_set "$KAFKA_CONFIG_FILE" "node.id" "$ID"
  316. fi
  317. else
  318. ID=$((POD_ID + KAFKA_MIN_ID))
  319. {{- if .Values.kraft.enabled }}
  320. kafka_conf_set "$KAFKA_CONFIG_FILE" "node.id" "$ID"
  321. {{- end }}
  322. {{- if or .Values.zookeeper.enabled .Values.externalZookeeper.servers }}
  323. kafka_conf_set "$KAFKA_CONFIG_FILE" "broker.id" "$ID"
  324. {{- end }}
  325. fi
  326. {{- if not .Values.listeners.advertisedListeners }}
  327. replace_placeholder "advertised-address-placeholder" "${MY_POD_NAME}.{{ $fullname }}-${POD_ROLE}-headless.{{ $releaseNamespace }}.svc.{{ $clusterDomain }}"
  328. if [[ "${EXTERNAL_ACCESS_ENABLED:-false}" =~ ^(yes|true)$ ]]; then
  329. configure_external_access
  330. fi
  331. {{- end }}
  332. {{- if (include "kafka.sslEnabled" .) }}
  333. configure_kafka_tls
  334. {{- end }}
  335. {{- if (include "kafka.saslEnabled" .) }}
  336. configure_kafka_sasl
  337. {{- end }}
  338. {{- if and .Values.tls.zookeeper.enabled .Values.tls.zookeeper.existingSecret }}
  339. configure_zookeeper_tls
  340. {{- end }}
  341. if [ -f /secret-config/server-secret.properties ]; then
  342. append_file_to_kafka_conf /secret-config/server-secret.properties $KAFKA_CONFIG_FILE
  343. fi
  344. {{- include "common.tplvalues.render" ( dict "value" .Values.extraInit "context" $ ) | nindent 4 }}