From 33ea0dac4d561e451df6b2463727fe26757ec281 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Mon, 27 Jul 2020 09:31:16 -0700 Subject: [PATCH] Add entrypoint to repo, move dokcer image from hacks/images to deploy/kicbase --- .github/workflows/kic_image.yml | 6 +- .github/workflows/master.yml | 40 ++-- .github/workflows/pr.yml | 40 ++-- Makefile | 2 +- .../kicbase/Dockerfile | 22 +- .../kicbase}/automount/minikube-automount | 0 .../automount/minikube-automount.service | 0 deploy/kicbase/entrypoint | 196 ++++++++++++++++++ 8 files changed, 253 insertions(+), 53 deletions(-) rename hack/images/kicbase.Dockerfile => deploy/kicbase/Dockerfile (91%) rename {hack/images => deploy/kicbase}/automount/minikube-automount (100%) rename {hack/images => deploy/kicbase}/automount/minikube-automount.service (100%) create mode 100755 deploy/kicbase/entrypoint diff --git a/.github/workflows/kic_image.yml b/.github/workflows/kic_image.yml index b7b7263e5ee3..46c9f273d377 100644 --- a/.github/workflows/kic_image.yml +++ b/.github/workflows/kic_image.yml @@ -2,7 +2,7 @@ name: KIC_IMAGE on: pull_request: paths: - - "hack/images/**" + - "deploy/kicbase/**" env: GOPROXY: https://proxy.golang.org jobs: @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Download Dependencies run: go mod download @@ -40,7 +40,7 @@ jobs: echo "end of debug stuff" echo $(which jq) - name: Build Image - run: | + run: | sudo apt-get install -y docker.io make kic-base-image docker images diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 42e1b954c9b9..863b260f974a 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -8,7 +8,7 @@ on: - "**.yml" - "**.yaml" - "Makefile" - - "!hack/images/**" + - "!deploy/kicbase/**" env: GOPROXY: https://proxy.golang.org jobs: @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Download Dependencies run: go mod download @@ -47,7 +47,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install libvirt run: | @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install libvirt run: | @@ -113,7 +113,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -196,7 +196,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -214,7 +214,7 @@ jobs: run: | hostname VBoxManage --version - sysctl hw.physicalcpu hw.logicalcpu + sysctl hw.physicalcpu hw.logicalcpu - name: Disable firewall run: | sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off @@ -337,7 +337,7 @@ jobs: echo "------------------------" - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install tools continue-on-error: true @@ -472,7 +472,7 @@ jobs: Get-WmiObject -class Win32_ComputerSystem - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install tools continue-on-error: true @@ -577,7 +577,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -672,7 +672,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -752,7 +752,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -770,11 +770,11 @@ jobs: run: | hostname VBoxManage --version - sysctl hw.physicalcpu hw.logicalcpu + sysctl hw.physicalcpu hw.logicalcpu - name: Disable firewall run: | sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off - sudo /usr/libexec/ApplicationFirewall/socketfilterfw -k + sudo /usr/libexec/ApplicationFirewall/socketfilterfw -k - name: Download Binaries uses: actions/download-artifact@v1 with: @@ -862,7 +862,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -944,7 +944,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -961,7 +961,7 @@ jobs: run: | hostname VBoxManage --version - sysctl hw.physicalcpu hw.logicalcpu + sysctl hw.physicalcpu hw.logicalcpu - name: Disable firewall run: | sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off @@ -1048,7 +1048,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -1128,7 +1128,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -1145,7 +1145,7 @@ jobs: run: | hostname VBoxManage --version - sysctl hw.physicalcpu hw.logicalcpu + sysctl hw.physicalcpu hw.logicalcpu - name: Disable firewall run: | sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ed72190a2e1d..b6ed544ed34a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -6,7 +6,7 @@ on: - "**.yml" - "**.yaml" - "Makefile" - - "!hack/images/**" + - "!deploy/kicbase/**" env: GOPROXY: https://proxy.golang.org jobs: @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Download Dependencies run: go mod download @@ -45,7 +45,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install libvirt run: | @@ -64,7 +64,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install libvirt run: | @@ -111,7 +111,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -194,7 +194,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -212,7 +212,7 @@ jobs: run: | hostname VBoxManage --version - sysctl hw.physicalcpu hw.logicalcpu + sysctl hw.physicalcpu hw.logicalcpu - name: Disable firewall run: | sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off @@ -335,7 +335,7 @@ jobs: echo "------------------------" - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install tools continue-on-error: true @@ -470,7 +470,7 @@ jobs: Get-WmiObject -class Win32_ComputerSystem - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install tools continue-on-error: true @@ -575,7 +575,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -670,7 +670,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -750,7 +750,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -768,11 +768,11 @@ jobs: run: | hostname VBoxManage --version - sysctl hw.physicalcpu hw.logicalcpu + sysctl hw.physicalcpu hw.logicalcpu - name: Disable firewall run: | sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off - sudo /usr/libexec/ApplicationFirewall/socketfilterfw -k + sudo /usr/libexec/ApplicationFirewall/socketfilterfw -k - name: Download Binaries uses: actions/download-artifact@v1 with: @@ -860,7 +860,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -942,7 +942,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -959,7 +959,7 @@ jobs: run: | hostname VBoxManage --version - sysctl hw.physicalcpu hw.logicalcpu + sysctl hw.physicalcpu hw.logicalcpu - name: Disable firewall run: | sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off @@ -1046,7 +1046,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -1126,7 +1126,7 @@ jobs: # go 1.14.6+ is needed because of this bug https://github.com/golang/go/issues/39308 - uses: actions/setup-go@v2 with: - go-version: '1.14.6' + go-version: "1.14.6" stable: true - name: Install gopogh @@ -1143,7 +1143,7 @@ jobs: run: | hostname VBoxManage --version - sysctl hw.physicalcpu hw.logicalcpu + sysctl hw.physicalcpu hw.logicalcpu - name: Disable firewall run: | sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off diff --git a/Makefile b/Makefile index 2fe1edbe2fd1..a7752fe40d36 100644 --- a/Makefile +++ b/Makefile @@ -532,7 +532,7 @@ storage-provisioner-image: out/storage-provisioner-$(GOARCH) ## Build storage-pr .PHONY: kic-base-image kic-base-image: ## builds the base image used for kic. docker rmi -f $(REGISTRY)/kicbase:$(KIC_VERSION)-snapshot || true - docker build -f ./hack/images/kicbase.Dockerfile -t local/kicbase:$(KIC_VERSION)-snapshot --build-arg COMMIT_SHA=${VERSION}-$(COMMIT) --cache-from $(REGISTRY)/kicbase:$(KIC_VERSION) --target base ./hack/images + docker build -f ./deploy/kicbase/Dockerfile -t local/kicbase:$(KIC_VERSION)-snapshot --build-arg COMMIT_SHA=${VERSION}-$(COMMIT) --cache-from $(REGISTRY)/kicbase:$(KIC_VERSION) --target base ./deploy/kicbase docker tag local/kicbase:$(KIC_VERSION)-snapshot $(REGISTRY)/kicbase:$(KIC_VERSION)-snapshot .PHONY: upload-preloaded-images-tar diff --git a/hack/images/kicbase.Dockerfile b/deploy/kicbase/Dockerfile similarity index 91% rename from hack/images/kicbase.Dockerfile rename to deploy/kicbase/Dockerfile index 9f3900415463..14b232b7a9bc 100644 --- a/hack/images/kicbase.Dockerfile +++ b/deploy/kicbase/Dockerfile @@ -47,7 +47,10 @@ RUN mkdir /var/run/sshd RUN echo 'root:root' |chpasswd RUN sed -ri 's/^#?PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config + # Add set -x to entrypoint file +COPY entrypoint /usr/local/bin/entrypoint +RUN chmod 755 /usr/local/bin/entrypoint RUN sed -i "20i set -x" /usr/local/bin/entrypoint EXPOSE 22 @@ -63,12 +66,13 @@ USER root RUN mkdir -p /kind # Deleting leftovers RUN apt-get clean -y && rm -rf \ - /var/cache/debconf/* \ - /var/lib/apt/lists/* \ - /var/log/* \ - /tmp/* \ - /var/tmp/* \ - /usr/share/doc/* \ - /usr/share/man/* \ - /usr/share/local/* \ - RUN echo "kic! Build: ${COMMIT_SHA} Time :$(date)" > "/kic.txt" + /var/cache/debconf/* \ + /var/lib/apt/lists/* \ + /var/log/* \ + /tmp/* \ + /var/tmp/* \ + /usr/share/doc/* \ + /usr/share/man/* \ + /usr/share/local/* + +RUN echo "kic! Build: ${COMMIT_SHA} Time :$(date)" > "/kic.txt" diff --git a/hack/images/automount/minikube-automount b/deploy/kicbase/automount/minikube-automount similarity index 100% rename from hack/images/automount/minikube-automount rename to deploy/kicbase/automount/minikube-automount diff --git a/hack/images/automount/minikube-automount.service b/deploy/kicbase/automount/minikube-automount.service similarity index 100% rename from hack/images/automount/minikube-automount.service rename to deploy/kicbase/automount/minikube-automount.service diff --git a/deploy/kicbase/entrypoint b/deploy/kicbase/entrypoint new file mode 100755 index 000000000000..07e1b43e4fb3 --- /dev/null +++ b/deploy/kicbase/entrypoint @@ -0,0 +1,196 @@ +#!/bin/bash + +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +fix_mount() { + echo 'INFO: ensuring we can execute /bin/mount even with userns-remap' + # necessary only when userns-remap is enabled on the host, but harmless + # The binary /bin/mount should be owned by root and have the setuid bit + chown root:root /bin/mount + chmod -s /bin/mount + + # This is a workaround to an AUFS bug that might cause `Text file + # busy` on `mount` command below. See more details in + # https://github.com/moby/moby/issues/9547 + if [[ "$(stat -f -c %T /bin/mount)" == 'aufs' ]]; then + echo 'INFO: detected aufs, calling sync' + sync + fi + + echo 'INFO: remounting /sys read-only' + # systemd-in-a-container should have read only /sys + # https://www.freedesktop.org/wiki/Software/systemd/ContainerInterface/ + # however, we need other things from `docker run --privileged` ... + # and this flag also happens to make /sys rw, amongst other things + mount -o remount,ro /sys + + echo 'INFO: making mounts shared' + # for mount propagation + mount --make-rshared / +} + +fix_cgroup() { + echo 'INFO: fix cgroup mounts for all subsystems' + # For each cgroup subsystem, Docker does a bind mount from the current + # cgroup to the root of the cgroup subsystem. For instance: + # /sys/fs/cgroup/memory/docker/ -> /sys/fs/cgroup/memory + # + # This will confuse Kubelet and cadvisor and will dump the following error + # messages in kubelet log: + # `summary_sys_containers.go:47] Failed to get system container stats for ".../kubelet.service"` + # + # This is because `/proc//cgroup` is not affected by the bind mount. + # The following is a workaround to recreate the original cgroup + # environment by doing another bind mount for each subsystem. + local docker_cgroup_mounts + docker_cgroup_mounts=$(grep /sys/fs/cgroup /proc/self/mountinfo | grep docker || true) + if [[ -n "${docker_cgroup_mounts}" ]]; then + local docker_cgroup cgroup_subsystems subsystem + docker_cgroup=$(echo "${docker_cgroup_mounts}" | head -n 1 | cut -d' ' -f 4) + cgroup_subsystems=$(echo "${docker_cgroup_mounts}" | cut -d' ' -f 5) + echo "${cgroup_subsystems}" | + while IFS= read -r subsystem; do + mkdir -p "${subsystem}${docker_cgroup}" + mount --bind "${subsystem}" "${subsystem}${docker_cgroup}" + done + fi +} + +fix_machine_id() { + # Deletes the machine-id embedded in the node image and generates a new one. + # This is necessary because both kubelet and other components like weave net + # use machine-id internally to distinguish nodes. + echo 'INFO: clearing and regenerating /etc/machine-id' + rm -f /etc/machine-id + systemd-machine-id-setup +} + +fix_product_name() { + # this is a small fix to hide the underlying hardware and fix issue #426 + # https://github.com/kubernetes-sigs/kind/issues/426 + if [[ -f /sys/class/dmi/id/product_name ]]; then + echo 'INFO: faking /sys/class/dmi/id/product_name to be "kind"' + echo 'kind' > /kind/product_name + mount -o ro,bind /kind/product_name /sys/class/dmi/id/product_name + fi +} + +fix_product_uuid() { + # The system UUID is usually read from DMI via sysfs, the problem is that + # in the kind case this means that all (container) nodes share the same + # system/product uuid, as they share the same DMI. + # Note: The UUID is read from DMI, this tool is overwriting the sysfs files + # which should fix the attached issue, but this workaround does not address + # the issue if a tool is reading directly from DMI. + # https://github.com/kubernetes-sigs/kind/issues/1027 + [[ ! -f /kind/product_uuid ]] && cat /proc/sys/kernel/random/uuid > /kind/product_uuid + if [[ -f /sys/class/dmi/id/product_uuid ]]; then + echo 'INFO: faking /sys/class/dmi/id/product_uuid to be random' + mount -o ro,bind /kind/product_uuid /sys/class/dmi/id/product_uuid + fi + if [[ -f /sys/devices/virtual/dmi/id/product_uuid ]]; then + echo 'INFO: faking /sys/devices/virtual/dmi/id/product_uuid as well' + mount -o ro,bind /kind/product_uuid /sys/devices/virtual/dmi/id/product_uuid + fi +} + +fix_kmsg() { + # In environments where /dev/kmsg is not available, the kubelet (1.15+) won't + # start because it cannot open /dev/kmsg when starting the kmsgparser in the + # OOM parser. + # To support those environments, we link /dev/kmsg to /dev/console. + # https://github.com/kubernetes-sigs/kind/issues/662 + if [[ ! -e /dev/kmsg ]]; then + if [[ -e /dev/console ]]; then + echo 'WARN: /dev/kmsg does not exist, symlinking /dev/console' >&2 + ln -s /dev/console /dev/kmsg + else + echo 'WARN: /dev/kmsg does not exist, nor does /dev/console!' >&2 + fi + fi +} + +configure_proxy() { + # ensure all processes receive the proxy settings by default + # https://www.freedesktop.org/software/systemd/man/systemd-system.conf.html + mkdir -p /etc/systemd/system.conf.d/ + cat </etc/systemd/system.conf.d/proxy-default-environment.conf +[Manager] +DefaultEnvironment="HTTP_PROXY=${HTTP_PROXY:-}" "HTTPS_PROXY=${HTTPS_PROXY:-}" "NO_PROXY=${NO_PROXY:-}" +EOF +} + +select_iptables() { + # based on: https://github.com/kubernetes/kubernetes/blob/ffe93b3979486feb41a0f85191bdd189cbd56ccc/build/debian-iptables/iptables-wrapper + local mode=nft + num_legacy_lines=$( (iptables-legacy-save || true; ip6tables-legacy-save || true) 2>/dev/null | grep '^-' | wc -l || true) + if [ "${num_legacy_lines}" -ge 10 ]; then + mode=legacy + else + num_nft_lines=$( (timeout 5 sh -c "iptables-nft-save; ip6tables-nft-save" || true) 2>/dev/null | grep '^-' | wc -l || true) + if [ "${num_legacy_lines}" -ge "${num_nft_lines}" ]; then + mode=legacy + fi + fi + + echo "INFO: setting iptables to detected mode: ${mode}" + update-alternatives --set iptables "/usr/sbin/iptables-${mode}" > /dev/null + update-alternatives --set ip6tables "/usr/sbin/ip6tables-${mode}" > /dev/null +} + +enable_network_magic(){ + # well-known docker embedded DNS is at 127.0.0.11:53 + local docker_embedded_dns_ip='127.0.0.11' + + # first we need to detect an IP to use for reaching the docker host + local docker_host_ip + docker_host_ip="$( (getent ahostsv4 'host.docker.internal' | head -n1 | cut -d' ' -f1) || true)" + if [[ -z "${docker_host_ip}" ]]; then + docker_host_ip=$(ip -4 route show default | cut -d' ' -f3) + fi + + # patch docker's iptables rules to switch out the DNS IP + iptables-save \ + | sed \ + `# switch docker DNS DNAT rules to our chosen IP` \ + -e "s/-d ${docker_embedded_dns_ip}/-d ${docker_host_ip}/g" \ + `# we need to also apply these rules to non-local traffic (from pods)` \ + -e 's/-A OUTPUT \(.*\) -j DOCKER_OUTPUT/\0\n-A PREROUTING \1 -j DOCKER_OUTPUT/' \ + `# switch docker DNS SNAT rules rules to our chosen IP` \ + -e "s/--to-source :53/--to-source ${docker_host_ip}:53/g"\ + | iptables-restore + + # now we can ensure that DNS is configured to use our IP + cp /etc/resolv.conf /etc/resolv.conf.original + sed -e "s/${docker_embedded_dns_ip}/${docker_host_ip}/g" /etc/resolv.conf.original >/etc/resolv.conf +} + +# run pre-init fixups +fix_kmsg +fix_mount +fix_cgroup +fix_machine_id +fix_product_name +fix_product_uuid +configure_proxy +select_iptables +enable_network_magic + +# we want the command (expected to be systemd) to be PID1, so exec to it +exec "$@"