From e108a81ae5ce4bf5bc63eabe3e7019a32096475d Mon Sep 17 00:00:00 2001 From: Viet Nguyen Duc Date: Tue, 16 Jul 2024 21:04:07 +0000 Subject: [PATCH] chart: proactive to set browser args via container env var Signed-off-by: Viet Nguyen Duc --- NodeChrome/wrap_chrome_binary | 11 +++++++- NodeChromium/wrap_chromium_binary | 26 ++++++++++++++++++- NodeEdge/wrap_edge_binary | 11 +++++++- README.md | 25 ++++++++++++++++++ charts/selenium-grid/README.md | 20 +++++++++++--- charts/selenium-grid/templates/_helpers.tpl | 12 ++++++++- charts/selenium-grid/values.yaml | 18 ++++++------- tests/docker-compose-v3-test-node-docker.yaml | 1 + tests/docker-compose-v3-test-parallel.yml | 3 ++- 9 files changed, 110 insertions(+), 17 deletions(-) diff --git a/NodeChrome/wrap_chrome_binary b/NodeChrome/wrap_chrome_binary index 58df2c406..f013d19d6 100755 --- a/NodeChrome/wrap_chrome_binary +++ b/NodeChrome/wrap_chrome_binary @@ -21,7 +21,16 @@ done # Set language environment variable export LANGUAGE="\$LANGUAGE" +# Capture the filtered environment variables start with "SE_BROWSER_ARGS_" into an array +mapfile -t BROWSER_ARGS_ARRAY < <(printenv | grep ^SE_BROWSER_ARGS_) +# Iterate over the array +for var in "\${BROWSER_ARGS_ARRAY[@]}"; do + # Split the variable into name and value + IFS='=' read -r name value <<< "\$var" + SE_BROWSER_ARGS="\$SE_BROWSER_ARGS \$value" +done + # Note: exec -a below is a bashism. -exec -a "\$0" "$BASE_PATH" --no-sandbox "\$@" +exec -a "\$0" "$BASE_PATH" --no-sandbox \$SE_BROWSER_ARGS "\$@" _EOF chmod +x "$WRAPPER_PATH" diff --git a/NodeChromium/wrap_chromium_binary b/NodeChromium/wrap_chromium_binary index 0d5947cad..bfe00753a 100755 --- a/NodeChromium/wrap_chromium_binary +++ b/NodeChromium/wrap_chromium_binary @@ -6,7 +6,31 @@ mv "$WRAPPER_PATH" "$BASE_PATH" cat > "$WRAPPER_PATH" <<_EOF #!/bin/bash + +# umask 002 ensures default permissions of files are 664 (rw-rw-r--) and directories are 775 (rwxrwxr-x). +umask 002 + +# Debian/Ubuntu seems to not respect --lang, it instead needs to be a LANGUAGE environment var +# See: https://stackoverflow.com/a/41893197/359999 +for var in "\$@"; do + if [[ \$var == --lang=* ]]; then + LANGUAGE=\${var//--lang=} + fi +done + +# Set language environment variable +export LANGUAGE="\$LANGUAGE" + +# Capture the filtered environment variables start with "SE_BROWSER_ARGS_" into an array +mapfile -t BROWSER_ARGS_ARRAY < <(printenv | grep ^SE_BROWSER_ARGS_) +# Iterate over the array +for var in "\${BROWSER_ARGS_ARRAY[@]}"; do + # Split the variable into name and value + IFS='=' read -r name value <<< "\$var" + SE_BROWSER_ARGS="\$SE_BROWSER_ARGS \$value" +done + # Note: exec -a below is a bashism. -exec -a "\$0" "$BASE_PATH" --no-sandbox "\$@" +exec -a "\$0" "$BASE_PATH" --no-sandbox \$SE_BROWSER_ARGS "\$@" _EOF chmod +x "$WRAPPER_PATH" diff --git a/NodeEdge/wrap_edge_binary b/NodeEdge/wrap_edge_binary index 31753b0d6..f5c9c7e93 100755 --- a/NodeEdge/wrap_edge_binary +++ b/NodeEdge/wrap_edge_binary @@ -21,7 +21,16 @@ done # Set language environment variable export LANGUAGE="\$LANGUAGE" +# Capture the filtered environment variables start with "SE_BROWSER_ARGS_" into an array +mapfile -t BROWSER_ARGS_ARRAY < <(printenv | grep ^SE_BROWSER_ARGS_) +# Iterate over the array +for var in "\${BROWSER_ARGS_ARRAY[@]}"; do + # Split the variable into name and value + IFS='=' read -r name value <<< "\$var" + SE_BROWSER_ARGS="\$SE_BROWSER_ARGS \$value" +done + # Note: exec -a below is a bashism. -exec -a "\$0" "$BASE_PATH" --no-sandbox "\$@" +exec -a "\$0" "$BASE_PATH" --no-sandbox \$SE_BROWSER_ARGS "\$@" _EOF chmod +x "$WRAPPER_PATH" diff --git a/README.md b/README.md index aaccdaf74..ee28f5d14 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Talk to us at https://www.selenium.dev/support/ * [Configuring the containers](#configuring-the-containers) * [SE_OPTS Selenium Configuration Options](#se_opts-selenium-configuration-options) * [SE_JAVA_OPTS Java Environment Options](#se_java_opts-java-environment-options) + * [SE_BROWSER_ARGS_* Add arguments for launching browser](#se_browser_args_-add-arguments-for-launching-browser) * [Node configuration options](#node-configuration-options) * [Node configuration relay commands](#node-configuration-relay-commands) * [Setting Sub Path](#setting-sub-path) @@ -978,6 +979,30 @@ You can pass `SE_JAVA_OPTS` environment variable to the Java process. $ docker run -d -p 4444:4444 -e SE_JAVA_OPTS=-Xmx512m --name selenium-hub selenium/hub:4.22.0-20240621 ``` +### SE_BROWSER_ARGS_* Add arguments for launching browser + +Instead of adding arguments via the browser options from language bindings, for example: + +```python +options = ChromeOptions() +options.add_argument('--incognito') +options.add_argument('--disable-dev-shm-usage') +driver = webdriver.Remote(options=options, command_executor="http://localhost:4444/wd/hub") +``` + +You also can proactive to force applying arguments directly from (node, standalone or node-docker) container environment variables. Define the environment variable with name starts with `SE_BROWSER_ARGS_` and following by config key is up to you (ensure those are unique when you define multiple arguments). For example: + +```bash +docker run -d -p 4444:4444 \ + -e SE_BROWSER_ARGS_INCOGNITO=--incognito \ + -e SE_BROWSER_ARGS_DISABLE_DSHM=--disable-dev-shm-usage \ + selenium/standalone-chrome:latest +``` + +[List chromium command-line arguments](https://peter.sh/experiments/chromium-command-line-switches/) for your reference. + +Note: Currently, this is applicable for node browsers Chrome/Chromium, Edge. + ### Node configuration options The Nodes register themselves through the Event Bus. When the Grid is started in its typical Hub/Node diff --git a/charts/selenium-grid/README.md b/charts/selenium-grid/README.md index 065fafcfb..b32739be8 100644 --- a/charts/selenium-grid/README.md +++ b/charts/selenium-grid/README.md @@ -23,6 +23,7 @@ This chart enables the creation of a Selenium Grid Server in Kubernetes. * [Configuration `global.K8S_PUBLIC_IP`](#configuration-globalk8s_public_ip) * [Configuration of Nodes](#configuration-of-nodes) * [Container ports and Service ports](#container-ports-and-service-ports) + * [Configuration of shm size limit for browser nodes](#configuration-of-shm-size-limit-for-browser-nodes) * [Configuration of Probes](#configuration-of-probes) * [Node Probes](#node-probes) * [Distributor Probes](#distributor-probes) @@ -384,6 +385,19 @@ edgeNode: protocol: TCP ``` +#### Configuration of shm size limit for browser nodes + +By default, node browsers (Chrome/Chromium, Edge) leave the config key `dshmVolumeSizeLimit` as empty. It means the `/dev/shm` volume mount is disabled, and argument `--disable-dev-shm-usage` is passed to the browser via container environment variable (get motivation from [this post](https://www.ministryoftesting.com/articles/navigating-chromedriver-crashes-in-kubernetes-a-tale-of-test-automation-resilience)). You can set another valid value to enable it back. For example: + +```yaml +chromeNode: + dshmVolumeSizeLimit: "2Gi" +edgeNode: + dshmVolumeSizeLimit: "2Gi" +``` + +For Firefox node, the default value is kept as `2Gi`. You can override it via `firefoxNode.dshmVolumeSizeLimit`. + ### Configuration of Probes #### Node Probes @@ -859,7 +873,7 @@ This table contains the configuration parameters of the chart and their default | `chromeNode.service.loadBalancerIP` | `` | Set specific loadBalancerIP when serviceType is LoadBalancer | | `chromeNode.service.ports` | `[]` | Extra ports exposed in node service | | `chromeNode.service.annotations` | `{}` | Custom annotations for service | -| `chromeNode.dshmVolumeSizeLimit` | `1Gi` | Size limit for DSH volume mounted in container (if not set, default is "1Gi") | +| `chromeNode.dshmVolumeSizeLimit` | `` | Size limit for DSH volume mounted in container (if not set, default is disabled, e.g "1Gi") | | `chromeNode.startupProbe.enabled` | `true` | Enable Probe to check pod is started successfully (the following configs see `values.yaml`) | | `chromeNode.readinessProbe.enabled` | `false` | Enable Readiness probe settings (the following configs see `values.yaml`) | | `chromeNode.livenessProbe.enabled` | `false` | Enable Liveness probe settings (the following configs see `values.yaml`) | @@ -902,7 +916,7 @@ This table contains the configuration parameters of the chart and their default | `firefoxNode.service.loadBalancerIP` | `` | Set specific loadBalancerIP when serviceType is LoadBalancer | | `firefoxNode.service.ports` | `[]` | Extra ports exposed in node service | | `firefoxNode.service.annotations` | `{}` | Custom annotations for service | -| `firefoxNode.dshmVolumeSizeLimit` | `1Gi` | Size limit for DSH volume mounted in container (if not set, default is "1Gi") | +| `firefoxNode.dshmVolumeSizeLimit` | `2Gi` | Size limit for DSH volume mounted in container (if not set, default is disabled, e.g "1Gi") | | `firefoxNode.startupProbe.enabled` | `true` | Enable Probe to check pod is started successfully (the following configs see `values.yaml`) | | `firefoxNode.readinessProbe.enabled` | `false` | Enable Readiness probe settings (the following configs see `values.yaml`) | | `firefoxNode.livenessProbe.enabled` | `false` | Enable Liveness probe settings (the following configs see `values.yaml`) | @@ -945,7 +959,7 @@ This table contains the configuration parameters of the chart and their default | `edgeNode.service.loadBalancerIP` | `` | Set specific loadBalancerIP when serviceType is LoadBalancer | | `edgeNode.service.ports` | `[]` | Extra ports exposed in node service | | `edgeNode.service.annotations` | `{}` | Custom annotations for service | -| `edgeNode.dshmVolumeSizeLimit` | `1Gi` | Size limit for DSH volume mounted in container (if not set, default is "1Gi") | +| `edgeNode.dshmVolumeSizeLimit` | `` | Size limit for DSH volume mounted in container (if not set, default is disabled, e.g "1Gi") | | `edgeNode.startupProbe.enabled` | `true` | Enable Probe to check pod is started successfully (the following configs see `values.yaml`) | | `edgeNode.readinessProbe.enabled` | `false` | Enable Readiness probe settings (the following configs see `values.yaml`) | | `edgeNode.livenessProbe.enabled` | `false` | Enable Liveness probe settings (the following configs see `values.yaml`) | diff --git a/charts/selenium-grid/templates/_helpers.tpl b/charts/selenium-grid/templates/_helpers.tpl index df56b7266..91aa95f69 100644 --- a/charts/selenium-grid/templates/_helpers.tpl +++ b/charts/selenium-grid/templates/_helpers.tpl @@ -250,6 +250,10 @@ template: image: {{ printf "%s/%s:%s" $imageRegistry .node.imageName $imageTag }} imagePullPolicy: {{ .node.imagePullPolicy }} env: + {{- if empty .node.dshmVolumeSizeLimit }} + - name: SE_BROWSER_ARGS_DISABLE_DSHM + value: "--disable-dev-shm-usage" + {{- end }} - name: SE_OTEL_SERVICE_NAME value: {{ .name | quote }} - name: SE_NODE_PORT @@ -294,8 +298,10 @@ template: {{- end }} {{- end }} volumeMounts: + {{- if not (empty .node.dshmVolumeSizeLimit) }} - name: dshm mountPath: /dev/shm + {{- end }} {{- range $fileName, $value := $.Values.nodeConfigMap.extraScripts }} - name: {{ tpl (default (include "seleniumGrid.node.configmap.fullname" $) $.Values.nodeConfigMap.scriptVolumeMountName) $ }} mountPath: {{ $.Values.nodeConfigMap.extraScriptsDirectory }}/{{ $fileName }} @@ -413,8 +419,10 @@ template: {{- end }} {{- end }} volumeMounts: + {{- if not (empty .node.dshmVolumeSizeLimit) }} - name: dshm mountPath: /dev/shm + {{- end }} {{- tpl (include "seleniumGrid.video.volumeMounts" .) $ | nindent 8 }} {{- with $.Values.videoRecorder.resources }} resources: {{- toYaml . | nindent 10 }} @@ -491,10 +499,12 @@ template: configMap: name: {{ template "seleniumGrid.node.configmap.fullname" $ }} defaultMode: {{ $.Values.nodeConfigMap.defaultMode }} + {{- if not (empty .node.dshmVolumeSizeLimit) }} - name: dshm emptyDir: medium: Memory - sizeLimit: {{ default "1Gi" .node.dshmVolumeSizeLimit }} + sizeLimit: {{ .node.dshmVolumeSizeLimit }} + {{- end }} {{- if eq (include "seleniumGrid.server.secureConnection" $) "true" }} - name: {{ include "seleniumGrid.tls.fullname" $ | quote }} secret: diff --git a/charts/selenium-grid/values.yaml b/charts/selenium-grid/values.yaml index ec3b39ea5..375cae778 100644 --- a/charts/selenium-grid/values.yaml +++ b/charts/selenium-grid/values.yaml @@ -724,7 +724,7 @@ chromeNode: memory: "1Gi" cpu: "1" limits: - memory: "1Gi" + memory: "2Gi" cpu: "1" # SecurityContext for chrome-node container securityContext: {} @@ -771,8 +771,8 @@ chromeNode: # targetPort: 5900 # Custom annotations for service annotations: {} - # Size limit for DSH volume mounted in container (if not set, default is "1Gi") - dshmVolumeSizeLimit: 1Gi + # Size limit for DSH volume mounted in container (if not set, default is disabled, e.g "1Gi") + dshmVolumeSizeLimit: "" # Priority class name for chrome-node pods priorityClassName: "" @@ -889,7 +889,7 @@ firefoxNode: memory: "1Gi" cpu: "1" limits: - memory: "1Gi" + memory: "2Gi" cpu: "1" # SecurityContext for firefox-node container securityContext: {} @@ -932,8 +932,8 @@ firefoxNode: # targetPort: 5900 # Custom annotations for service annotations: {} - # Size limit for DSH volume mounted in container (if not set, default is "1Gi") - dshmVolumeSizeLimit: 1Gi + # Size limit for DSH volume mounted in container (if not set, default is disabled, e.g "1Gi") + dshmVolumeSizeLimit: "2Gi" # Priority class name for firefox-node pods priorityClassName: "" @@ -1048,7 +1048,7 @@ edgeNode: memory: "1Gi" cpu: "1" limits: - memory: "1Gi" + memory: "2Gi" cpu: "1" # SecurityContext for edge-node container securityContext: {} @@ -1091,8 +1091,8 @@ edgeNode: # targetPort: 5900 # Custom annotations for service annotations: {} - # Size limit for DSH volume mounted in container (if not set, default is "1Gi") - dshmVolumeSizeLimit: 1Gi + # Size limit for DSH volume mounted in container (if not set, default is disabled, e.g "1Gi") + dshmVolumeSizeLimit: "" # Priority class name for edge-node pods priorityClassName: "" diff --git a/tests/docker-compose-v3-test-node-docker.yaml b/tests/docker-compose-v3-test-node-docker.yaml index ecbb2c8d0..4c9bc4ada 100644 --- a/tests/docker-compose-v3-test-node-docker.yaml +++ b/tests/docker-compose-v3-test-node-docker.yaml @@ -20,6 +20,7 @@ services: - SE_EVENT_BUS_PUBLISH_PORT=4442 - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 - SE_NODE_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS} + - SE_BROWSER_ARGS_DISABLE_DSHM=--disable-dev-shm-usage - SE_LOG_LEVEL=${LOG_LEVEL} selenium-hub: diff --git a/tests/docker-compose-v3-test-parallel.yml b/tests/docker-compose-v3-test-parallel.yml index 9276d2536..dea72a1a5 100644 --- a/tests/docker-compose-v3-test-parallel.yml +++ b/tests/docker-compose-v3-test-parallel.yml @@ -12,7 +12,6 @@ services: replicas: 10 image: selenium/node-${NODE_CHROME}:${TAG} user: ${UID} - shm_size: 2gb depends_on: - selenium-hub environment: @@ -21,6 +20,8 @@ services: - SE_EVENT_BUS_PUBLISH_PORT=4442 - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 - SE_NODE_ENABLE_MANAGED_DOWNLOADS=true + - SE_BROWSER_ARGS_DISABLE_DSHM=--disable-dev-shm-usage + - SE_BROWSER_ARGS_INCOGNITO=--incognito --incognito - SE_LOG_LEVEL=${LOG_LEVEL} firefox: