Repository: kubernetes/ingress-nginx
Pull request #8841 (2022-07-19 20:31:28)

version (commit sha) bump for dependency github.com/moul/pb due to dependency licence
<!--- Provide a general summary of your changes in the Title above --->
<!--- Please don't @-mention people in PR or commit messages (do so in an additional comment). --->

## What this PR does / why we need it:
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
The project dependency github.com/moul/pb was using non-licensed version (the license was added later). I did a dependency update to the latest commit, which does include the license.

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation only

## Which issue/s this PR fixes
<!--
(optional, in `fixes #<issue number>` format, will close that issue when PR gets merged):

fixes #
-->

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I've read the [CONTRIBUTION](https://github.com/kubernetes/ingress-nginx/blob/main/CONTRIBUTING.md) guide
- [ ] I have added tests to cover my changes.
- [x] All new and existing tests passed.


linux-foundation-easycla[bot]:  <a href="https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/18706487/72891330/8841/#/?version=2"><img src="https://s3.amazonaws.com/cla-project-logo-prod/cla-not-signed.svg" alt="CLA Not Signed" align="left" height="28" width="328"></a><br/><br /><ul><li><a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/18706487/72891330/8841/#/?version=2' target='_blank'>:x:</a> - login: @europ / name: Adrián Tóth . The commit (ac888c36f48a49b0fd58d64efed7c977cd818471) is not authorized under a signed CLA. <a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/18706487/72891330/8841/#/?version=2' target='_blank'>Please click here to be authorized</a>. For further assistance with EasyCLA, <a href='https://jira.linuxfoundation.org/servicedesk/customer/portal/4' target='_blank'>please submit a support request ticket</a>.</li></ul>
k8s-ci-robot: Welcome @europ! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes/ingress-nginx'>kubernetes/ingress-nginx</a> 🎉. Please refer to our [pull request process documentation](https://git.k8s.io/community/contributors/guide/pull-requests.md) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes/ingress-nginx has [its own contribution guidelines](https://github.com/kubernetes/ingress-nginx/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/ingress-nginx/pull/8841#" title="Author self-approved">europ</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign strongjz for approval by writing `/assign @strongjz` in a comment. For more information see:[The Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes/ingress-nginx/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["strongjz"]} -->

Patch:
diff --git a/Makefile b/Makefile
index bafddeef2..5ba8e8ed5 100644
--- a/Makefile
+++ b/Makefile
@@ -54,8 +54,9 @@ ifneq ($(PLATFORM),)
 endif
 
 MAC_OS = $(shell uname -s)
+
 ifeq ($(MAC_OS), Darwin)
-	MAC_DOCKER_FLAGS="--load"
+	MAC_DOCKER_FLAGS=
 else
 	MAC_DOCKER_FLAGS=
 endif
diff --git a/NGINX_BASE b/NGINX_BASE
index cab849353..f0d8b2fd8 100644
--- a/NGINX_BASE
+++ b/NGINX_BASE
@@ -1,1 +1,1 @@
-registry.k8s.io/ingress-nginx/nginx:e1a16f6e74ef43cdfd59de75b3394d6b20ef7645@sha256:283c8c6132ebaeb155c927ea6e8ae0cd834a4a05d4807eceafa1b6e44d875911
+registry.k8s.io/ingress-nginx/nginx:c5766dc011965f22fac2f4437e86d0fd9960a50c@sha256:94ff9b435a5f3f4570bbdaed4a3a523f63a54ce2ad6b132b5640bae2af5d9d90
diff --git a/build/build.sh b/build/build.sh
index 210a8e10f..3b51d665e 100755
--- a/build/build.sh
+++ b/build/build.sh
@@ -49,6 +49,7 @@ echo "Building targets for ${ARCH}, generated targets in ${TARGETS_DIR} director
 
 echo "Building ${PKG}/cmd/nginx"
 
+git config --add safe.directory /go/src/k8s.io/ingress-nginx
 ${GO_BUILD_CMD} \
   -trimpath -ldflags="-buildid= -w -s \
     -X ${PKG}/version.RELEASE=${TAG} \
diff --git a/build/dev-env.sh b/build/dev-env.sh
index c0c99526b..68b683350 100755
--- a/build/dev-env.sh
+++ b/build/dev-env.sh
@@ -36,7 +36,7 @@ if ! command -v kind &> /dev/null; then
 fi
 
 if ! command -v kubectl &> /dev/null; then
-  echo "Please install kubectl 1.15 or higher"
+  echo "Please install kubectl 1.24.0 or higher"
   exit 1
 fi
 
@@ -46,14 +46,14 @@ if ! command -v helm &> /dev/null; then
 fi
 
 HELM_VERSION=$(helm version 2>&1 | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+') || true
-if [[ ${HELM_VERSION} < "v3.0.0" ]]; then
-  echo "Please upgrade helm to v3.0.0 or higher"
+if [[ ${HELM_VERSION} < "v3.9.0" ]]; then
+  echo "Please upgrade helm to v3.9.0 or higher"
   exit 1
 fi
 
 KUBE_CLIENT_VERSION=$(kubectl version --client --short 2>/dev/null | grep Client | awk '{print $3}' | cut -d. -f2) || true
-if [[ ${KUBE_CLIENT_VERSION} -lt 14 ]]; then
-  echo "Please update kubectl to 1.15 or higher"
+if [[ ${KUBE_CLIENT_VERSION} -lt 24 ]]; then
+  echo "Please update kubectl to 1.24.2 or higher"
   exit 1
 fi
 
@@ -61,7 +61,7 @@ echo "[dev-env] building image"
 make build image
 docker tag "${REGISTRY}/controller:${TAG}" "${DEV_IMAGE}"
 
-export K8S_VERSION=${K8S_VERSION:-v1.21.12@sha256:f316b33dd88f8196379f38feb80545ef3ed44d9197dca1bfd48bcb1583210207}
+export K8S_VERSION=${K8S_VERSION:-v1.24.2@sha256:1f0cee2282f43150b52dc7933183ed96abdcfc8d293f30ec07082495874876f1}
 
 KIND_CLUSTER_NAME="ingress-nginx-dev"
 
diff --git a/build/run-in-docker.sh b/build/run-in-docker.sh
index d2cfe9552..7fdc20699 100755
--- a/build/run-in-docker.sh
+++ b/build/run-in-docker.sh
@@ -65,12 +65,14 @@ fi
 
 USER=${USER:-nobody}
 
+MAC_OS="`uname -s`"
 MAC_OS="${MAC_OS:-}"
 if [[ ${MAC_OS} == "Darwin" ]]; then
 	MAC_DOCKER_FLAGS=""
 else
 	MAC_DOCKER_FLAGS="-u $(id -u ${USER}):$(id -g ${USER})" #idk why mac/git fails on the gobuild if these are presented to dockerrun.sh script
 fi
+echo "MAC_OS = ${MAC_OS}, MAC_OS_FLAGS = ${MAC_DOCKER_FLAGS}"
 
 echo "..printing env & other vars to stdout"
 echo "HOSTNAME=`hostname`"
@@ -84,7 +86,7 @@ if [[ "$DOCKER_IN_DOCKER_ENABLED" == "true" ]]; then
   echo "FLAGS=$FLAGS"
   go env
   set -x
-  go install -mod=mod github.com/onsi/ginkgo/ginkgo@v1.16.4
+  go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo@v2.1.4
   find / -type f -name ginkgo 2>/dev/null
   which ginkgo
   /bin/bash -c "${FLAGS}"
diff --git a/cmd/dataplane/main.go b/cmd/dataplane/main.go
new file mode 100644
index 000000000..8ea59a412
--- /dev/null
+++ b/cmd/dataplane/main.go
@@ -0,0 +1,101 @@
+/*
+Copyright 2022 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.
+*/
+
+package main
+
+import (
+	"fmt"
+	"math/rand" // #nosec
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/collectors"
+
+	"k8s.io/klog/v2"
+
+	"k8s.io/ingress-nginx/internal/ingress/controller"
+	"k8s.io/ingress-nginx/internal/ingress/metric"
+	"k8s.io/ingress-nginx/internal/nginx"
+	ingressflags "k8s.io/ingress-nginx/pkg/flags"
+	"k8s.io/ingress-nginx/pkg/metrics"
+	"k8s.io/ingress-nginx/pkg/util/file"
+	"k8s.io/ingress-nginx/pkg/util/process"
+	"k8s.io/ingress-nginx/version"
+)
+
+func main() {
+	klog.InitFlags(nil)
+
+	rand.Seed(time.Now().UnixNano())
+
+	fmt.Println(version.String())
+	var err error
+	showVersion, conf, err := ingressflags.ParseFlags()
+	if showVersion {
+		os.Exit(0)
+	}
+
+	if err != nil {
+		klog.Fatal(err)
+	}
+
+	err = file.CreateRequiredDirectories()
+	if err != nil {
+		klog.Fatal(err)
+	}
+
+	reg := prometheus.NewRegistry()
+
+	reg.MustRegister(collectors.NewGoCollector())
+	reg.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{
+		PidFn:        func() (int, error) { return os.Getpid(), nil },
+		ReportErrors: true,
+	}))
+
+	mc := metric.NewDummyCollector()
+	if conf.EnableMetrics {
+		// TODO: Ingress class is not a part of dataplane anymore
+		mc, err = metric.NewCollector(conf.MetricsPerHost, conf.ReportStatusClasses, reg, conf.IngressClassConfiguration.Controller, *conf.MetricsBuckets)
+		if err != nil {
+			klog.Fatalf("Error creating prometheus collector:  %v", err)
+		}
+	}
+	// Pass the ValidationWebhook status to determine if we need to start the collector
+	// for the admissionWebhook
+	// TODO: Dataplane does not contain validation webhook so the MetricCollector should not receive
+	// this as an argument
+	mc.Start(conf.ValidationWebhook)
+
+	if conf.EnableProfiling {
+		// TODO: Turn Profiler address configurable via flags
+		go metrics.RegisterProfiler("127.0.0.1", nginx.ProfilerPort)
+	}
+
+	ngx := controller.NewNGINXController(conf, mc)
+
+	mux := http.NewServeMux()
+	metrics.RegisterHealthz(nginx.HealthPath, mux)
+	metrics.RegisterMetrics(reg, mux)
+
+	go metrics.StartHTTPServer(conf.HealthCheckHost, conf.ListenPorts.Health, mux)
+	go ngx.Start()
+
+	process.HandleSigterm(ngx, conf.PostShutdownGracePeriod, func(code int) {
+		os.Exit(code)
+	})
+}
diff --git a/cmd/nginx/main.go b/cmd/nginx/main.go
index ff5f0c365..c585ed95e 100644
--- a/cmd/nginx/main.go
+++ b/cmd/nginx/main.go
@@ -21,34 +21,34 @@ import (
 	"fmt"
 	"math/rand" // #nosec
 	"net/http"
-	"net/http/pprof"
 	"os"
-	"os/signal"
 	"path/filepath"
 	"runtime"
-	"syscall"
 	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
-	"github.com/prometheus/client_golang/prometheus/promhttp"
+	"github.com/prometheus/client_golang/prometheus/collectors"
 	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/wait"
 	discovery "k8s.io/apimachinery/pkg/version"
-	"k8s.io/apiserver/pkg/server/healthz"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
 	certutil "k8s.io/client-go/util/cert"
 	"k8s.io/klog/v2"
 
-	"k8s.io/ingress-nginx/internal/file"
 	"k8s.io/ingress-nginx/internal/ingress/controller"
 	"k8s.io/ingress-nginx/internal/ingress/metric"
 	"k8s.io/ingress-nginx/internal/k8s"
 	"k8s.io/ingress-nginx/internal/net/ssl"
 	"k8s.io/ingress-nginx/internal/nginx"
+	"k8s.io/ingress-nginx/pkg/util/file"
 	"k8s.io/ingress-nginx/version"
+
+	ingressflags "k8s.io/ingress-nginx/pkg/flags"
+	"k8s.io/ingress-nginx/pkg/metrics"
+	"k8s.io/ingress-nginx/pkg/util/process"
 )
 
 func main() {
@@ -58,7 +58,7 @@ func main() {
 
 	fmt.Println(version.String())
 
-	showVersion, conf, err := parseFlags()
+	showVersion, conf, err := ingressflags.ParseFlags()
 	if showVersion {
 		os.Exit(0)
 	}
@@ -125,8 +125,8 @@ func main() {
 
 	reg := prometheus.NewRegistry()
 
-	reg.MustRegister(prometheus.NewGoCollector())
-	reg.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{
+	reg.MustRegister(collectors.NewGoCollector())
+	reg.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{
 		PidFn:        func() (int, error) { return os.Getpid(), nil },
 		ReportErrors: true,
 	}))
@@ -143,14 +143,14 @@ func main() {
 	mc.Start(conf.ValidationWebhook)
 
 	if conf.EnableProfiling {
-		go registerProfiler()
+		go metrics.RegisterProfiler("127.0.0.1", nginx.ProfilerPort)
 	}
 
 	ngx := controller.NewNGINXController(conf, mc)
 
 	mux := http.NewServeMux()
-	registerHealthz(nginx.HealthPath, ngx, mux)
-	registerMetrics(reg, mux)
+	metrics.RegisterHealthz(nginx.HealthPath, mux, ngx)
+	metrics.RegisterMetrics(reg, mux)
 
 	_, errExists := os.Stat("/chroot")
 	if errExists == nil {
@@ -159,35 +159,14 @@ func main() {
 
 	}
 
-	go startHTTPServer(conf.HealthCheckHost, conf.ListenPorts.Health, mux)
+	go metrics.StartHTTPServer(conf.HealthCheckHost, conf.ListenPorts.Health, mux)
 	go ngx.Start()
 
-	handleSigterm(ngx, conf.PostShutdownGracePeriod, func(code int) {
+	process.HandleSigterm(ngx, conf.PostShutdownGracePeriod, func(code int) {
 		os.Exit(code)
 	})
 }
 
-type exiter func(code int)
-
-func handleSigterm(ngx *controller.NGINXController, delay int, exit exiter) {
-	signalChan := make(chan os.Signal, 1)
-	signal.Notify(signalChan, syscall.SIGTERM)
-	<-signalChan
-	klog.InfoS("Received SIGTERM, shutting down")
-
-	exitCode := 0
-	if err := ngx.Stop(); err != nil {
-		klog.Warningf("Error during shutdown: %v", err)
-		exitCode = 1
-	}
-
-	klog.Infof("Handled quit, delaying controller exit for %d seconds", delay)
-	time.Sleep(time.Duration(delay) * time.Second)
-
-	klog.InfoS("Exiting", "code", exitCode)
-	exit(exitCode)
-}
-
 // createApiserverClient creates a new Kubernetes REST client. apiserverHost is
 // the URL of the API server in the format protocol://address:port/pathPrefix,
 // kubeConfig is the location of a kubeconfig file. If defined, the kubeconfig
@@ -293,60 +272,6 @@ func handleFatalInitError(err error) {
 		err)
 }
 
-func registerHealthz(healthPath string, ic *controller.NGINXController, mux *http.ServeMux) {
-	// expose health check endpoint (/healthz)
-	healthz.InstallPathHandler(mux,
-		healthPath,
-		healthz.PingHealthz,
-		ic,
-	)
-}
-
-func registerMetrics(reg *prometheus.Registry, mux *http.ServeMux) {
-	mux.Handle(
-		"/metrics",
-		promhttp.InstrumentMetricHandler(
-			reg,
-			promhttp.HandlerFor(reg, promhttp.HandlerOpts{}),
-		),
-	)
-}
-
-func registerProfiler() {
-	mux := http.NewServeMux()
-
-	mux.HandleFunc("/debug/pprof/", pprof.Index)
-	mux.HandleFunc("/debug/pprof/heap", pprof.Index)
-	mux.HandleFunc("/debug/pprof/mutex", pprof.Index)
-	mux.HandleFunc("/debug/pprof/goroutine", pprof.Index)
-	mux.HandleFunc("/debug/pprof/threadcreate", pprof.Index)
-	mux.HandleFunc("/debug/pprof/block", pprof.Index)
-	mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
-	mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
-	mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
-	mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
-
-	server := &http.Server{
-		Addr: fmt.Sprintf("127.0.0.1:%v", nginx.ProfilerPort),
-		//G112 (CWE-400): Potential Slowloris Attack
-		ReadHeaderTimeout: 10 * time.Second,
-		Handler:           mux,
-	}
-	klog.Fatal(server.ListenAndServe())
-}
-
-func startHTTPServer(host string, port int, mux *http.ServeMux) {
-	server := &http.Server{
-		Addr:              fmt.Sprintf("%s:%v", host, port),
-		Handler:           mux,
-		ReadTimeout:       10 * time.Second,
-		ReadHeaderTimeout: 10 * time.Second,
-		WriteTimeout:      300 * time.Second,
-		IdleTimeout:       120 * time.Second,
-	}
-	klog.Fatal(server.ListenAndServe())
-}
-
 func checkService(key string, kubeClient *kubernetes.Clientset) error {
 	ns, name, err := k8s.ParseNameNS(key)
 	if err != nil {
diff --git a/docs/examples/customization/sysctl/patch.json b/docs/examples/customization/sysctl/patch.json
index 683b4ec6b..319ccf4f8 100644
--- a/docs/examples/customization/sysctl/patch.json
+++ b/docs/examples/customization/sysctl/patch.json
@@ -4,7 +4,7 @@
 			"spec": {
 				"initContainers": [{
 					"name": "sysctl",
-					"image": "alpine:3.16.0",
+					"image": "alpine:3.16.1",
 					"securityContext": {
 						"privileged": true
 					},
diff --git a/docs/index.md b/docs/index.md
index 91661cba3..5687bf5d3 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -228,3 +228,13 @@ If you start Ingress-Nginx B with the command line argument `--watch-ingress-wit
   --set controller.ingressClassByName=true
   ```
 - If you need to install yet another instance, then repeat the procedure to create a new namespace, change the values such as names & namespaces (for example from "-2" to "-3"), or anything else that meets your needs.
+- If you need to install all instances in the same namespace, then you need to specify a different **election id**, like this:
+  ```
+  helm install ingress-nginx-2 ingress-nginx/ingress-nginx  \
+  --namespace kube-system \
+  --set controller.electionID=nginx-two-leader \
+  --set controller.ingressClassResource.name=nginx-two \
+  --set controller.ingressClassResource.controllerValue="example.com/ingress-nginx-2" \
+  --set controller.ingressClassResource.enabled=true \
+  --set controller.ingressClassByName=true
+  ```
\ No newline at end of file
diff --git a/docs/user-guide/multiple-ingress.md b/docs/user-guide/multiple-ingress.md
index 4825b8458..35e0e45dc 100644
--- a/docs/user-guide/multiple-ingress.md
+++ b/docs/user-guide/multiple-ingress.md
@@ -8,7 +8,7 @@ To fix this problem, use [IngressClasses](https://kubernetes.io/docs/concepts/se
 
 If all ingress controllers respect IngressClasses (e.g. multiple instances of ingress-nginx v1.0), you can deploy two Ingress controllers by granting them control over two different IngressClasses, then selecting one of the two IngressClasses with `ingressClassName`.
 
-First, ensure the `--controller-class=` and `--ingress-class` are set to something different on each ingress controller:
+First, ensure the `--controller-class=` and `--ingress-class` are set to something different on each ingress controller, If your additional ingress controller is to be installed in a namespace, where there is/are one/more-than-one ingress-nginx-controller(s) already installed, then you need to specify a different unique `--election-id` for the new instance of the controller.
 
 ```yaml
 # ingress-nginx Deployment/Statefulset
@@ -19,6 +19,7 @@ spec:
          - name: ingress-nginx-internal-controller
            args:
              - /nginx-ingress-controller
+             - '--election-id=ingress-controller-leader'
              - '--controller-class=k8s.io/internal-ingress-nginx'
              - '--ingress-class=k8s.io/internal-nginx'
             ...
@@ -53,6 +54,7 @@ or if installing with Helm:
 
 ```yaml
 controller:
+  electionID: ingress-controller-leader
   ingressClassResource:
     name: internal-nginx  # default: nginx
     enabled: true
diff --git a/docs/user-guide/nginx-configuration/configmap.md b/docs/user-guide/nginx-configuration/configmap.md
index 270dd1c62..e5af31cb5 100755
--- a/docs/user-guide/nginx-configuration/configmap.md
+++ b/docs/user-guide/nginx-configuration/configmap.md
@@ -479,7 +479,7 @@ Example for json output:
 
 ```json
 
-log-format-upstream: '{"time": "$time_iso8601", "remote_addr": "$proxy_protocol_addr", "x_forward_for": "$proxy_add_x_forwarded_for", "request_id": "$req_id",
+log-format-upstream: '{"time": "$time_iso8601", "remote_addr": "$proxy_protocol_addr", "x_forwarded_for": "$proxy_add_x_forwarded_for", "request_id": "$req_id",
   "remote_user": "$remote_user", "bytes_sent": $bytes_sent, "request_time": $request_time, "status": $status, "vhost": "$host", "request_proto": "$server_protocol",
   "path": "$uri", "request_query": "$args", "request_length": $request_length, "duration": $request_time,"method": "$request_method", "http_referrer": "$http_referer",
   "http_user_agent": "$http_user_agent" }'
diff --git a/go.mod b/go.mod
index c72359736..748678dbf 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,7 @@ require (
 	github.com/mitchellh/go-ps v1.0.0
 	github.com/mitchellh/hashstructure v1.1.0
 	github.com/mitchellh/mapstructure v1.5.0
-	github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52
+	github.com/moul/pb v0.0.0-20220425114252-bca18df4138c
 	github.com/ncabatoff/process-exporter v0.7.10
 	github.com/onsi/ginkgo v1.16.5
 	github.com/opencontainers/runc v1.1.3
diff --git a/go.sum b/go.sum
index 34bd73d10..951da0363 100644
--- a/go.sum
+++ b/go.sum
@@ -443,6 +443,8 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/
 github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
 github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52 h1:8zDEa5yAIWYBHSDpPbSgGIBL/SvPSE9/FlB3aQ54d/A=
 github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52/go.mod h1:jE2HT8eoucYyUPBFJMreiVlC3KPHkDMtN8wn+ef7Y64=
+github.com/moul/pb v0.0.0-20220425114252-bca18df4138c h1:1STmblv9zmHLDpru4dbnf1PNL6wrrZNf7yBH+SfQU+s=
+github.com/moul/pb v0.0.0-20220425114252-bca18df4138c/go.mod h1:jE2HT8eoucYyUPBFJMreiVlC3KPHkDMtN8wn+ef7Y64=
 github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
diff --git a/hack/generate-deploy-scripts.sh b/hack/generate-deploy-scripts.sh
index c85f21705..7b4f304ad 100755
--- a/hack/generate-deploy-scripts.sh
+++ b/hack/generate-deploy-scripts.sh
@@ -55,11 +55,11 @@ do
       --namespace ingress-nginx \
       --kube-version ${K8S_VERSION} \
       > $MANIFEST
-    sed -i '' '/app.kubernetes.io\/managed-by: Helm/d' $MANIFEST
-    sed -i '' '/helm.sh/d' $MANIFEST
+    sed -i.bak '/app.kubernetes.io\/managed-by: Helm/d' $MANIFEST
+    sed -i.bak '/helm.sh/d' $MANIFEST
 
     kustomize --load-restrictor=LoadRestrictionsNone build . > ${OUTPUT_DIR}/deploy.yaml
-    rm $MANIFEST
+    rm $MANIFEST $MANIFEST.bak
     cd ~-
     # automatically generate the (unsupported) kustomization.yaml for each target
     sed "s_{TARGET}_${TARGET}_" $TEMPLATE_DIR/static-kustomization-template.yaml > ${OUTPUT_DIR}/kustomization.yaml
@@ -68,7 +68,7 @@ do
     if [[ ${K8S_VERSION} = ${K8S_DEFAULT_VERSION} ]]
     then
       cp ${OUTPUT_DIR}/*.yaml ${OUTPUT_DIR}/../
-      sed -i "s/^/#GENERATED FOR K8S ${K8S_VERSION}\n/" ${OUTPUT_DIR}/../deploy.yaml
+      sed -i.bak "s/^/#GENERATED FOR K8S ${K8S_VERSION}\n/" ${OUTPUT_DIR}/../deploy.yaml && rm ${OUTPUT_DIR}/../deploy.yaml.bak
     fi
   done
 done
diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh
index 9023a3a1a..fadc67633 100755
--- a/hack/update-codegen.sh
+++ b/hack/update-codegen.sh
@@ -38,7 +38,7 @@ chmod +x ${CODEGEN_PKG}/generate-groups.sh
 #  nginxingress:v1alpha1 \
 #  --output-base "$(dirname ${BASH_SOURCE})/../../.."
 ${CODEGEN_PKG}/generate-groups.sh "deepcopy" \
-  k8s.io/ingress-nginx/internal k8s.io/ingress-nginx/internal \
+  k8s.io/ingress-nginx/internal k8s.io/ingress-nginx/pkg/apis \
   .:ingress \
   --output-base "$(dirname ${BASH_SOURCE})/../../.." \
   --go-header-file ${SCRIPT_ROOT}/hack/boilerplate/boilerplate.generated.go.txt
diff --git a/images/cfssl/rootfs/Dockerfile b/images/cfssl/rootfs/Dockerfile
index bfe19cfd9..59a89d92c 100644
--- a/images/cfssl/rootfs/Dockerfile
+++ b/images/cfssl/rootfs/Dockerfile
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM alpine:3.16.0
+FROM alpine:3.16.1
 
 RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
 RUN apk add --no-cache \
diff --git a/images/httpbin/rootfs/Dockerfile b/images/httpbin/rootfs/Dockerfile
index 5ee58d69c..016ab913f 100644
--- a/images/httpbin/rootfs/Dockerfile
+++ b/images/httpbin/rootfs/Dockerfile
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM alpine:3.16.0
+FROM alpine:3.16.1
 
 ENV LC_ALL=C.UTF-8
 ENV LANG=C.UTF-8
diff --git a/images/nginx/rc.yaml b/images/nginx/rc.yaml
index 2936cf14b..bcf2b0274 100644
--- a/images/nginx/rc.yaml
+++ b/images/nginx/rc.yaml
@@ -38,7 +38,7 @@ spec:
     spec:
       containers:
         - name: nginx
-          image: registry.k8s.io/ingress-nginx/nginx:0ff500c23f34e939305de709cb6d47da34b66611@sha256:15f91034a03550dfab6ec50a7be4abbb683d087e234ad7fef5adedef54e46a5a
+          image: registry.k8s.io/ingress-nginx/nginx:c5766dc011965f22fac2f4437e86d0fd9960a50c@sha256:94ff9b435a5f3f4570bbdaed4a3a523f63a54ce2ad6b132b5640bae2af5d9d90
           ports:
             - containerPort: 80
             - containerPort: 443
diff --git a/images/nginx/rootfs/Dockerfile b/images/nginx/rootfs/Dockerfile
index fd3dfe5ba..a93a58f8f 100644
--- a/images/nginx/rootfs/Dockerfile
+++ b/images/nginx/rootfs/Dockerfile
@@ -11,7 +11,7 @@
 # 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.
-FROM alpine:3.16.0 as builder
+FROM alpine:3.16.1 as builder
 
 COPY . /
 
@@ -21,7 +21,7 @@ RUN apk update \
   && /build.sh
 
 # Use a multi-stage build
-FROM alpine:3.16.0
+FROM alpine:3.16.1
 
 ENV PATH=$PATH:/usr/local/luajit/bin:/usr/local/nginx/sbin:/usr/local/nginx/bin
 
diff --git a/images/nginx/rootfs/build.sh b/images/nginx/rootfs/build.sh
index c23ed8681..f3efb3663 100755
--- a/images/nginx/rootfs/build.sh
+++ b/images/nginx/rootfs/build.sh
@@ -53,8 +53,8 @@ export JAEGER_VERSION=0.7.0
 # Check for recent changes: https://github.com/msgpack/msgpack-c/compare/cpp-3.3.0...master
 export MSGPACK_VERSION=3.3.0
 
-# Check for recent changes: https://github.com/DataDog/dd-opentracing-cpp/compare/v1.3.0...master
-export DATADOG_CPP_VERSION=af53c523787cca108ae9f458ea5c962e48187a36
+# Check for recent changes: https://github.com/DataDog/dd-opentracing-cpp/compare/v1.3.2...master
+export DATADOG_CPP_VERSION=1.3.2
 
 # Check for recent changes: https://github.com/SpiderLabs/ModSecurity-nginx/compare/v1.0.2...master
 export MODSECURITY_VERSION=1.0.2
@@ -264,8 +264,8 @@ get_src 1ee6dad809a5bb22efb45e6dac767f7ce544ad652d353a93d7f26b605f69fe3f \
         "https://github.com/openresty/luajit2/archive/v$LUAJIT_VERSION.tar.gz"
 fi
 
-get_src f29393f2cd9288105a0029a6a324fe1f7558a9e7e852d59a6355f7581bb90e30 \
-        "https://github.com/DataDog/dd-opentracing-cpp/archive/$DATADOG_CPP_VERSION.tar.gz"
+get_src 586f92166018cc27080d34e17c59d68219b85af745edf3cc9fe41403fc9b4ac6 \
+        "https://github.com/DataDog/dd-opentracing-cpp/archive/v$DATADOG_CPP_VERSION.tar.gz"
 
 get_src 1af5a5632dc8b00ae103d51b7bf225de3a7f0df82f5c6a401996c080106e600e \
         "https://github.com/influxdata/nginx-influxdb-module/archive/$NGINX_INFLUXDB_VERSION.tar.gz"
diff --git a/images/opentelemetry/rootfs/Dockerfile b/images/opentelemetry/rootfs/Dockerfile
index 18fea3d51..ccab2223d 100644
--- a/images/opentelemetry/rootfs/Dockerfile
+++ b/images/opentelemetry/rootfs/Dockerfile
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 
-FROM alpine:3.16.0 as base
+FROM alpine:3.16.1 as base
 
 RUN mkdir -p /opt/third_party/install
 COPY . /opt/third_party/
@@ -39,7 +39,7 @@ COPY --from=grpc /opt/third_party/install/ /usr
 COPY --from=otel-cpp /opt/third_party/install/ /usr
 RUN bash /opt/third_party/build.sh -n
 
-FROM alpine:3.16.0
+FROM alpine:3.16.1
 COPY --from=base /opt/third_party/init_module.sh /usr/local/bin/init_module.sh
 COPY --from=nginx /etc/nginx/modules /etc/nginx/modules
 COPY --from=nginx /opt/third_party/install/lib /etc/nginx/modules
diff --git a/internal/ingress/annotations/auth/main.go b/internal/ingress/annotations/auth/main.go
index e05988365..58486c6e8 100644
--- a/internal/ingress/annotations/auth/main.go
+++ b/internal/ingress/annotations/auth/main.go
@@ -26,10 +26,10 @@ import (
 	networking "k8s.io/api/networking/v1"
 	"k8s.io/client-go/tools/cache"
 
-	"k8s.io/ingress-nginx/internal/file"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
+	"k8s.io/ingress-nginx/pkg/util/file"
 )
 
 var (
diff --git a/internal/ingress/annotations/authreq/main.go b/internal/ingress/annotations/authreq/main.go
index b5dc4c70b..b607f5482 100644
--- a/internal/ingress/annotations/authreq/main.go
+++ b/internal/ingress/annotations/authreq/main.go
@@ -28,7 +28,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
-	"k8s.io/ingress-nginx/internal/sets"
+	"k8s.io/ingress-nginx/pkg/util/sets"
 )
 
 // Config returns external authentication configuration for an Ingress rule
diff --git a/internal/ingress/annotations/globalratelimit/main.go b/internal/ingress/annotations/globalratelimit/main.go
index c5763d0cb..ea9fc4678 100644
--- a/internal/ingress/annotations/globalratelimit/main.go
+++ b/internal/ingress/annotations/globalratelimit/main.go
@@ -27,7 +27,7 @@ import (
 	ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
 	"k8s.io/ingress-nginx/internal/net"
-	"k8s.io/ingress-nginx/internal/sets"
+	"k8s.io/ingress-nginx/pkg/util/sets"
 )
 
 const defaultKey = "$remote_addr"
diff --git a/internal/ingress/annotations/ipwhitelist/main.go b/internal/ingress/annotations/ipwhitelist/main.go
index 38610ade6..63c049fef 100644
--- a/internal/ingress/annotations/ipwhitelist/main.go
+++ b/internal/ingress/annotations/ipwhitelist/main.go
@@ -27,7 +27,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
-	"k8s.io/ingress-nginx/internal/sets"
+	"k8s.io/ingress-nginx/pkg/util/sets"
 )
 
 // SourceRange returns the CIDR
diff --git a/internal/ingress/annotations/ratelimit/main.go b/internal/ingress/annotations/ratelimit/main.go
index 4011c2542..84a5f10f0 100644
--- a/internal/ingress/annotations/ratelimit/main.go
+++ b/internal/ingress/annotations/ratelimit/main.go
@@ -26,7 +26,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
 	"k8s.io/ingress-nginx/internal/net"
-	"k8s.io/ingress-nginx/internal/sets"
+	"k8s.io/ingress-nginx/pkg/util/sets"
 )
 
 const (
diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go
index 07f9d957a..4ee2df965 100644
--- a/internal/ingress/controller/config/config.go
+++ b/internal/ingress/controller/config/config.go
@@ -24,9 +24,9 @@ import (
 
 	apiv1 "k8s.io/api/core/v1"
 
-	"k8s.io/ingress-nginx/internal/ingress"
 	"k8s.io/ingress-nginx/internal/ingress/defaults"
-	"k8s.io/ingress-nginx/internal/runtime"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
+	"k8s.io/ingress-nginx/pkg/util/runtime"
 )
 
 var (
diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go
index 44385f175..d0f85bb01 100644
--- a/internal/ingress/controller/controller.go
+++ b/internal/ingress/controller/controller.go
@@ -32,7 +32,6 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/wait"
 	clientset "k8s.io/client-go/kubernetes"
-	"k8s.io/ingress-nginx/internal/ingress"
 	"k8s.io/ingress-nginx/internal/ingress/annotations"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/log"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
@@ -45,6 +44,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/metric/collectors"
 	"k8s.io/ingress-nginx/internal/k8s"
 	"k8s.io/ingress-nginx/internal/nginx"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 	"k8s.io/klog/v2"
 )
 
diff --git a/internal/ingress/controller/endpoints.go b/internal/ingress/controller/endpoints.go
index 26d7f298e..5615fadea 100644
--- a/internal/ingress/controller/endpoints.go
+++ b/internal/ingress/controller/endpoints.go
@@ -29,8 +29,8 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 
-	"k8s.io/ingress-nginx/internal/ingress"
 	"k8s.io/ingress-nginx/internal/k8s"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 // getEndpoints returns a list of Endpoint structs for a given service/target port combination.
diff --git a/internal/ingress/controller/location.go b/internal/ingress/controller/location.go
index d40e88960..9776f8c87 100644
--- a/internal/ingress/controller/location.go
+++ b/internal/ingress/controller/location.go
@@ -21,7 +21,7 @@ import (
 	"strings"
 
 	networking "k8s.io/api/networking/v1"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 var (
diff --git a/internal/ingress/controller/nginx.go b/internal/ingress/controller/nginx.go
index 9c8e5265c..d0f4abe27 100644
--- a/internal/ingress/controller/nginx.go
+++ b/internal/ingress/controller/nginx.go
@@ -44,11 +44,9 @@ import (
 	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
 	"k8s.io/client-go/tools/record"
 	"k8s.io/client-go/util/flowcontrol"
-	"k8s.io/klog/v2"
+	"k8s.io/ingress-nginx/pkg/tcpproxy"
 
 	adm_controller "k8s.io/ingress-nginx/internal/admission/controller"
-	"k8s.io/ingress-nginx/internal/file"
-	"k8s.io/ingress-nginx/internal/ingress"
 	ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
 	"k8s.io/ingress-nginx/internal/ingress/controller/process"
 	"k8s.io/ingress-nginx/internal/ingress/controller/store"
@@ -60,7 +58,10 @@ import (
 	"k8s.io/ingress-nginx/internal/net/ssl"
 	"k8s.io/ingress-nginx/internal/nginx"
 	"k8s.io/ingress-nginx/internal/task"
-	"k8s.io/ingress-nginx/internal/watch"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
+
+	"k8s.io/ingress-nginx/pkg/util/file"
+	klog "k8s.io/klog/v2"
 )
 
 const (
@@ -101,7 +102,7 @@ func NewNGINXController(config *Configuration, mc metric.Collector) *NGINXContro
 
 		runningConfig: new(ingress.Configuration),
 
-		Proxy: &TCPProxy{},
+		Proxy: &tcpproxy.TCPProxy{},
 
 		metricCollector: mc,
 
@@ -171,7 +172,7 @@ func NewNGINXController(config *Configuration, mc metric.Collector) *NGINXContro
 
 	n.t = ngxTpl
 
-	_, err = watch.NewFileWatcher(nginx.TemplatePath, onTemplateChange)
+	_, err = file.NewFileWatcher(nginx.TemplatePath, onTemplateChange)
 	if err != nil {
 		klog.Fatalf("Error creating file watcher for %v: %v", nginx.TemplatePath, err)
 	}
@@ -195,7 +196,7 @@ func NewNGINXController(config *Configuration, mc metric.Collector) *NGINXContro
 	}
 
 	for _, f := range filesToWatch {
-		_, err = watch.NewFileWatcher(f, func() {
+		_, err = file.NewFileWatcher(f, func() {
 			klog.InfoS("File changed detected. Reloading NGINX", "path", f)
 			n.syncQueue.EnqueueTask(task.GetDummyObject("file-change"))
 		})
@@ -241,7 +242,7 @@ type NGINXController struct {
 
 	isShuttingDown bool
 
-	Proxy *TCPProxy
+	Proxy *tcpproxy.TCPProxy
 
 	store store.Storer
 
@@ -439,7 +440,7 @@ func (n NGINXController) DefaultEndpoint() ingress.Endpoint {
 func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressCfg ingress.Configuration) ([]byte, error) {
 
 	if n.cfg.EnableSSLPassthrough {
-		servers := []*TCPServer{}
+		servers := []*tcpproxy.TCPServer{}
 		for _, pb := range ingressCfg.PassthroughBackends {
 			svc := pb.Service
 			if svc == nil {
@@ -464,7 +465,7 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
 			}
 
 			// TODO: Allow PassthroughBackends to specify they support proxy-protocol
-			servers = append(servers, &TCPServer{
+			servers = append(servers, &tcpproxy.TCPServer{
 				Hostname:      pb.Hostname,
 				IP:            svc.Spec.ClusterIP,
 				Port:          port,
@@ -751,8 +752,8 @@ func (n *NGINXController) setupSSLProxy() {
 	proxyPort := n.cfg.ListenPorts.SSLProxy
 
 	klog.InfoS("Starting TLS proxy for SSL Passthrough")
-	n.Proxy = &TCPProxy{
-		Default: &TCPServer{
+	n.Proxy = &tcpproxy.TCPProxy{
+		Default: &tcpproxy.TCPServer{
 			Hostname:      "localhost",
 			IP:            "127.0.0.1",
 			Port:          proxyPort,
diff --git a/internal/ingress/controller/store/backend_ssl.go b/internal/ingress/controller/store/backend_ssl.go
index 19283d2fd..6f0fcf189 100644
--- a/internal/ingress/controller/store/backend_ssl.go
+++ b/internal/ingress/controller/store/backend_ssl.go
@@ -20,15 +20,17 @@ import (
 	"fmt"
 	"strings"
 
-	"k8s.io/klog/v2"
-
 	apiv1 "k8s.io/api/core/v1"
 	networking "k8s.io/api/networking/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	"k8s.io/ingress-nginx/internal/file"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
+
+	klog "k8s.io/klog/v2"
+
 	"k8s.io/ingress-nginx/internal/net/ssl"
+
+	"k8s.io/ingress-nginx/pkg/util/file"
 )
 
 // syncSecret synchronizes the content of a TLS Secret (certificate(s), secret
diff --git a/internal/ingress/controller/store/ingress.go b/internal/ingress/controller/store/ingress.go
index 1d8ccb342..d05b2624b 100644
--- a/internal/ingress/controller/store/ingress.go
+++ b/internal/ingress/controller/store/ingress.go
@@ -19,7 +19,7 @@ package store
 import (
 	networking "k8s.io/api/networking/v1"
 	"k8s.io/client-go/tools/cache"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 // IngressLister makes a Store that lists Ingress.
diff --git a/internal/ingress/controller/store/ingress_annotation.go b/internal/ingress/controller/store/ingress_annotation.go
index c9c596d1e..b70ceb1c6 100644
--- a/internal/ingress/controller/store/ingress_annotation.go
+++ b/internal/ingress/controller/store/ingress_annotation.go
@@ -18,7 +18,7 @@ package store
 
 import (
 	"k8s.io/client-go/tools/cache"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 // IngressWithAnnotationsLister makes a Store that lists Ingress rules with annotations already parsed
diff --git a/internal/ingress/controller/store/local_secret.go b/internal/ingress/controller/store/local_secret.go
index ee3ced398..8b26e7a53 100644
--- a/internal/ingress/controller/store/local_secret.go
+++ b/internal/ingress/controller/store/local_secret.go
@@ -21,7 +21,7 @@ import (
 
 	"k8s.io/client-go/tools/cache"
 
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 // SSLCertTracker holds a store of referenced Secrets in Ingress rules
diff --git a/internal/ingress/controller/store/store.go b/internal/ingress/controller/store/store.go
index d29636b03..239fbaaf5 100644
--- a/internal/ingress/controller/store/store.go
+++ b/internal/ingress/controller/store/store.go
@@ -43,10 +43,11 @@ import (
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/tools/record"
 	"k8s.io/ingress-nginx/internal/ingress/inspector"
-	"k8s.io/klog/v2"
 
-	"k8s.io/ingress-nginx/internal/file"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/internal/nginx"
+	"k8s.io/ingress-nginx/pkg/util/file"
+	klog "k8s.io/klog/v2"
+
 	"k8s.io/ingress-nginx/internal/ingress/annotations"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
@@ -56,7 +57,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/errors"
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
 	"k8s.io/ingress-nginx/internal/k8s"
-	"k8s.io/ingress-nginx/internal/nginx"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 // IngressFilterFunc decides if an Ingress should be omitted or not
diff --git a/internal/ingress/controller/template/configmap.go b/internal/ingress/controller/template/configmap.go
index bcd985f7f..35a0e4536 100644
--- a/internal/ingress/controller/template/configmap.go
+++ b/internal/ingress/controller/template/configmap.go
@@ -34,7 +34,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	"k8s.io/ingress-nginx/internal/ingress/controller/config"
 	ing_net "k8s.io/ingress-nginx/internal/net"
-	"k8s.io/ingress-nginx/internal/runtime"
+	"k8s.io/ingress-nginx/pkg/util/runtime"
 )
 
 const (
diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go
index a331c196a..8d4cb6e75 100644
--- a/internal/ingress/controller/template/template.go
+++ b/internal/ingress/controller/template/template.go
@@ -40,12 +40,12 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/klog/v2"
 
-	"k8s.io/ingress-nginx/internal/ingress"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
 	"k8s.io/ingress-nginx/internal/ingress/controller/config"
 	ing_net "k8s.io/ingress-nginx/internal/net"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 const (
diff --git a/internal/ingress/controller/util.go b/internal/ingress/controller/util.go
index a8232ed0e..6562dd17c 100644
--- a/internal/ingress/controller/util.go
+++ b/internal/ingress/controller/util.go
@@ -28,7 +28,7 @@ import (
 	api "k8s.io/api/core/v1"
 	networking "k8s.io/api/networking/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 	klog "k8s.io/klog/v2"
 )
 
diff --git a/internal/ingress/metric/collectors/controller.go b/internal/ingress/metric/collectors/controller.go
index e4e2655c8..5822f0d57 100644
--- a/internal/ingress/metric/collectors/controller.go
+++ b/internal/ingress/metric/collectors/controller.go
@@ -22,7 +22,7 @@ import (
 
 	"github.com/prometheus/client_golang/prometheus"
 	"k8s.io/apimachinery/pkg/util/sets"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 	"k8s.io/ingress-nginx/version"
 	"k8s.io/klog/v2"
 )
diff --git a/internal/ingress/metric/dummy.go b/internal/ingress/metric/dummy.go
index 8360dc87d..4a6366b84 100644
--- a/internal/ingress/metric/dummy.go
+++ b/internal/ingress/metric/dummy.go
@@ -18,7 +18,7 @@ package metric
 
 import (
 	"k8s.io/apimachinery/pkg/util/sets"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 // NewDummyCollector returns a dummy metric collector
diff --git a/internal/ingress/metric/main.go b/internal/ingress/metric/main.go
index a0aea1e71..b3323c7fe 100644
--- a/internal/ingress/metric/main.go
+++ b/internal/ingress/metric/main.go
@@ -25,8 +25,8 @@ import (
 	"k8s.io/klog/v2"
 
 	"k8s.io/apimachinery/pkg/util/sets"
-	"k8s.io/ingress-nginx/internal/ingress"
 	"k8s.io/ingress-nginx/internal/ingress/metric/collectors"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 // Collector defines the interface for a metric collector
diff --git a/internal/ingress/status/status.go b/internal/ingress/status/status.go
index bf79701fc..2e53682a0 100644
--- a/internal/ingress/status/status.go
+++ b/internal/ingress/status/status.go
@@ -34,9 +34,9 @@ import (
 	"k8s.io/apimachinery/pkg/util/wait"
 	clientset "k8s.io/client-go/kubernetes"
 
-	"k8s.io/ingress-nginx/internal/ingress"
 	"k8s.io/ingress-nginx/internal/k8s"
 	"k8s.io/ingress-nginx/internal/task"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 // UpdateInterval defines the time interval, in seconds, in
diff --git a/internal/net/ssl/ssl.go b/internal/net/ssl/ssl.go
index bb99e2e8a..05ec10bc5 100644
--- a/internal/net/ssl/ssl.go
+++ b/internal/net/ssl/ssl.go
@@ -39,11 +39,13 @@ import (
 
 	"github.com/zakjan/cert-chain-resolver/certUtil"
 	"k8s.io/apimachinery/pkg/util/sets"
-	"k8s.io/ingress-nginx/internal/file"
-	"k8s.io/ingress-nginx/internal/ingress"
+
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
+
 	ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
-	"k8s.io/ingress-nginx/internal/watch"
-	"k8s.io/klog/v2"
+	"k8s.io/ingress-nginx/pkg/util/file"
+
+	klog "k8s.io/klog/v2"
 )
 
 // FakeSSLCertificateUID defines the default UID to use for the fake SSL
@@ -506,8 +508,8 @@ func NewTLSListener(certificate, key string) *TLSListener {
 
 	l.load()
 
-	_, _ = watch.NewFileWatcher(certificate, l.load)
-	_, _ = watch.NewFileWatcher(key, l.load)
+	_, _ = file.NewFileWatcher(certificate, l.load)
+	_, _ = file.NewFileWatcher(key, l.load)
 
 	return &l
 }
diff --git a/internal/ingress/sslcert.go b/pkg/apis/ingress/sslcert.go
similarity index 100%
rename from internal/ingress/sslcert.go
rename to pkg/apis/ingress/sslcert.go
diff --git a/internal/ingress/types.go b/pkg/apis/ingress/types.go
similarity index 98%
rename from internal/ingress/types.go
rename to pkg/apis/ingress/types.go
index db4f37f99..8460ee684 100644
--- a/internal/ingress/types.go
+++ b/pkg/apis/ingress/types.go
@@ -42,13 +42,8 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
 )
 
-var (
-	// DefaultSSLDirectory defines the location where the SSL certificates will be generated
-	// This directory contains all the SSL certificates that are specified in Ingress rules.
-	// The name of each file is <namespace>-<secret name>.pem. The content is the concatenated
-	// certificate and key.
-	DefaultSSLDirectory = "/ingress-controller/ssl"
-)
+// TODO: The API shouldn't be importing structs from annotation code. Instead we probably want a conversion from internal
+// to API object, or see how much effort is to move the structs of annotations to become an "API'ish" as well
 
 // Configuration holds the definition of all the parts required to describe all
 // ingresses reachable by the ingress controller (using a filter by namespace)
diff --git a/internal/ingress/types_equals.go b/pkg/apis/ingress/types_equals.go
similarity index 99%
rename from internal/ingress/types_equals.go
rename to pkg/apis/ingress/types_equals.go
index 3e39940a3..1ba80d07e 100644
--- a/internal/ingress/types_equals.go
+++ b/pkg/apis/ingress/types_equals.go
@@ -17,7 +17,7 @@ limitations under the License.
 package ingress
 
 import (
-	"k8s.io/ingress-nginx/internal/sets"
+	"k8s.io/ingress-nginx/pkg/util/sets"
 )
 
 // Equal tests for equality between two Configuration types
diff --git a/internal/ingress/zz_generated.deepcopy.go b/pkg/apis/ingress/zz_generated.deepcopy.go
similarity index 100%
rename from internal/ingress/zz_generated.deepcopy.go
rename to pkg/apis/ingress/zz_generated.deepcopy.go
diff --git a/cmd/nginx/flags.go b/pkg/flags/flags.go
similarity index 96%
rename from cmd/nginx/flags.go
rename to pkg/flags/flags.go
index 9a00b58b0..f7c1771e8 100644
--- a/cmd/nginx/flags.go
+++ b/pkg/flags/flags.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package main
+package flags
 
 import (
 	"flag"
@@ -37,7 +37,11 @@ import (
 	klog "k8s.io/klog/v2"
 )
 
-func parseFlags() (bool, *controller.Configuration, error) {
+// TODO: We should split the flags functions between common for all programs
+// and specific for each component (like webhook, controller and configurer)
+// ParseFlags generates a configuration for Ingress Controller based on the flags
+// provided by users
+func ParseFlags() (bool, *controller.Configuration, error) {
 	var (
 		flags = pflag.NewFlagSet("", pflag.ExitOnError)
 
@@ -379,3 +383,12 @@ https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-g
 
 	return false, config, err
 }
+
+// ResetForTesting clears all flag state and sets the usage function as directed.
+// After calling resetForTesting, parse errors in flag handling will not
+// exit the program.
+// Extracted from https://github.com/golang/go/blob/master/src/flag/export_test.go
+func ResetForTesting(usage func()) {
+	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
+	flag.Usage = usage
+}
diff --git a/pkg/metrics/handler.go b/pkg/metrics/handler.go
new file mode 100644
index 000000000..c37c1760c
--- /dev/null
+++ b/pkg/metrics/handler.go
@@ -0,0 +1,87 @@
+/*
+Copyright 2022 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.
+*/
+
+package metrics
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/pprof"
+	"time"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+	"k8s.io/apiserver/pkg/server/healthz"
+	klog "k8s.io/klog/v2"
+)
+
+func RegisterHealthz(healthPath string, mux *http.ServeMux, checks ...healthz.HealthChecker) {
+
+	healthCheck := []healthz.HealthChecker{healthz.PingHealthz}
+	if len(checks) > 0 {
+		healthCheck = append(healthCheck, checks...)
+	}
+	// expose health check endpoint (/healthz)
+	healthz.InstallPathHandler(mux,
+		healthPath,
+		healthCheck...,
+	)
+}
+
+func RegisterMetrics(reg *prometheus.Registry, mux *http.ServeMux) {
+	mux.Handle(
+		"/metrics",
+		promhttp.InstrumentMetricHandler(
+			reg,
+			promhttp.HandlerFor(reg, promhttp.HandlerOpts{}),
+		),
+	)
+}
+
+func RegisterProfiler(host string, port int) {
+	mux := http.NewServeMux()
+
+	mux.HandleFunc("/debug/pprof/", pprof.Index)
+	mux.HandleFunc("/debug/pprof/heap", pprof.Index)
+	mux.HandleFunc("/debug/pprof/mutex", pprof.Index)
+	mux.HandleFunc("/debug/pprof/goroutine", pprof.Index)
+	mux.HandleFunc("/debug/pprof/threadcreate", pprof.Index)
+	mux.HandleFunc("/debug/pprof/block", pprof.Index)
+	mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
+	mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
+	mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
+	mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
+
+	server := &http.Server{
+		Addr: fmt.Sprintf("%s:%d", host, port),
+		//G112 (CWE-400): Potential Slowloris Attack
+		ReadHeaderTimeout: 10 * time.Second,
+		Handler:           mux,
+	}
+	klog.Fatal(server.ListenAndServe())
+}
+
+func StartHTTPServer(host string, port int, mux *http.ServeMux) {
+	server := &http.Server{
+		Addr:              fmt.Sprintf("%s:%v", host, port),
+		Handler:           mux,
+		ReadTimeout:       10 * time.Second,
+		ReadHeaderTimeout: 10 * time.Second,
+		WriteTimeout:      300 * time.Second,
+		IdleTimeout:       120 * time.Second,
+	}
+	klog.Fatal(server.ListenAndServe())
+}
diff --git a/internal/ingress/controller/tcp.go b/pkg/tcpproxy/tcp.go
similarity index 99%
rename from internal/ingress/controller/tcp.go
rename to pkg/tcpproxy/tcp.go
index eedecc71a..7bbff80b4 100644
--- a/internal/ingress/controller/tcp.go
+++ b/pkg/tcpproxy/tcp.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package controller
+package tcpproxy
 
 import (
 	"fmt"
diff --git a/internal/file/file.go b/pkg/util/file/file.go
similarity index 100%
rename from internal/file/file.go
rename to pkg/util/file/file.go
diff --git a/internal/file/file_test.go b/pkg/util/file/file_test.go
similarity index 100%
rename from internal/file/file_test.go
rename to pkg/util/file/file_test.go
diff --git a/internal/watch/file_watcher.go b/pkg/util/file/file_watcher.go
similarity index 99%
rename from internal/watch/file_watcher.go
rename to pkg/util/file/file_watcher.go
index b393045b2..eeb7b5721 100644
--- a/internal/watch/file_watcher.go
+++ b/pkg/util/file/file_watcher.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package watch
+package file
 
 import (
 	"log"
diff --git a/internal/file/filesystem.go b/pkg/util/file/filesystem.go
similarity index 100%
rename from internal/file/filesystem.go
rename to pkg/util/file/filesystem.go
diff --git a/internal/file/structure.go b/pkg/util/file/structure.go
similarity index 100%
rename from internal/file/structure.go
rename to pkg/util/file/structure.go
diff --git a/pkg/util/process/controller.go b/pkg/util/process/controller.go
new file mode 100644
index 000000000..ae9bc9356
--- /dev/null
+++ b/pkg/util/process/controller.go
@@ -0,0 +1,24 @@
+/*
+Copyright 2022 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.
+*/
+
+package process
+
+// ProcessController defines a common interface for a process to be controlled,
+// like the configurer, the webhook or the proper ingress controller
+type ProcessController interface {
+	Start()
+	Stop() error
+}
diff --git a/pkg/util/process/sigterm.go b/pkg/util/process/sigterm.go
new file mode 100644
index 000000000..77c0ad58c
--- /dev/null
+++ b/pkg/util/process/sigterm.go
@@ -0,0 +1,49 @@
+/*
+Copyright 2022 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.
+*/
+
+package process
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+
+	klog "k8s.io/klog/v2"
+)
+
+type exiter func(code int)
+
+// HandleSigterm receives a ProcessController interface and deals with
+// the graceful shutdown
+func HandleSigterm(ngx ProcessController, delay int, exit exiter) {
+	signalChan := make(chan os.Signal, 1)
+	signal.Notify(signalChan, syscall.SIGTERM)
+	<-signalChan
+	klog.InfoS("Received SIGTERM, shutting down")
+
+	exitCode := 0
+	if err := ngx.Stop(); err != nil {
+		klog.Warningf("Error during shutdown: %v", err)
+		exitCode = 1
+	}
+
+	klog.Infof("Handled quit, delaying controller exit for %d seconds", delay)
+	time.Sleep(time.Duration(delay) * time.Second)
+
+	klog.InfoS("Exiting", "code", exitCode)
+	exit(exitCode)
+}
diff --git a/internal/runtime/cpu_linux.go b/pkg/util/runtime/cpu_linux.go
similarity index 100%
rename from internal/runtime/cpu_linux.go
rename to pkg/util/runtime/cpu_linux.go
diff --git a/internal/runtime/cpu_notlinux.go b/pkg/util/runtime/cpu_notlinux.go
similarity index 100%
rename from internal/runtime/cpu_notlinux.go
rename to pkg/util/runtime/cpu_notlinux.go
diff --git a/internal/sets/match.go b/pkg/util/sets/match.go
similarity index 100%
rename from internal/sets/match.go
rename to pkg/util/sets/match.go
diff --git a/internal/sets/match_test.go b/pkg/util/sets/match_test.go
similarity index 100%
rename from internal/sets/match_test.go
rename to pkg/util/sets/match_test.go
diff --git a/rootfs/Dockerfile-chroot b/rootfs/Dockerfile-chroot
index 69d2a0f71..de2fd6da0 100644
--- a/rootfs/Dockerfile-chroot
+++ b/rootfs/Dockerfile-chroot
@@ -23,7 +23,7 @@ RUN apk update \
   && apk upgrade \
   && /chroot.sh
 
-FROM alpine:3.16.0
+FROM alpine:3.16.1
 
 ARG TARGETARCH
 ARG VERSION


Test patch:
diff --git a/cmd/nginx/main_test.go b/cmd/nginx/main_test.go
index 2a29953ad..f57c02c5e 100644
--- a/cmd/nginx/main_test.go
+++ b/cmd/nginx/main_test.go
@@ -33,6 +33,8 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/controller"
 	"k8s.io/ingress-nginx/internal/k8s"
 	"k8s.io/ingress-nginx/internal/nginx"
+	ingressflags "k8s.io/ingress-nginx/pkg/flags"
+	"k8s.io/ingress-nginx/pkg/util/process"
 )
 
 func TestCreateApiserverClient(t *testing.T) {
@@ -83,7 +85,7 @@ func TestHandleSigterm(t *testing.T) {
 		t.Fatalf("error creating pod %v: %v", pod, err)
 	}
 
-	resetForTesting(func() { t.Fatal("bad parse") })
+	ingressflags.ResetForTesting(func() { t.Fatal("bad parse") })
 
 	os.Setenv("POD_NAME", podName)
 	os.Setenv("POD_NAMESPACE", namespace)
@@ -97,7 +99,7 @@ func TestHandleSigterm(t *testing.T) {
 	}()
 
 	os.Args = []string{"cmd", "--default-backend-service", "ingress-nginx/default-backend-http", "--http-port", "0", "--https-port", "0"}
-	_, conf, err := parseFlags()
+	_, conf, err := ingressflags.ParseFlags()
 	if err != nil {
 		t.Errorf("Unexpected error creating NGINX controller: %v", err)
 	}
@@ -105,7 +107,7 @@ func TestHandleSigterm(t *testing.T) {
 
 	ngx := controller.NewNGINXController(conf, nil)
 
-	go handleSigterm(ngx, 10, func(code int) {
+	go process.HandleSigterm(ngx, 10, func(code int) {
 		if code != 1 {
 			t.Errorf("Expected exit code 1 but %d received", code)
 		}
diff --git a/images/test-runner/Makefile b/images/test-runner/Makefile
index 85569a083..0f2b72bad 100644
--- a/images/test-runner/Makefile
+++ b/images/test-runner/Makefile
@@ -41,7 +41,7 @@ build: ensure-buildx
 		--build-arg BASE_IMAGE=$(NGINX_BASE_IMAGE) \
 		--build-arg GOLANG_VERSION=1.18.2 \
 		--build-arg ETCD_VERSION=3.4.3-0 \
-		--build-arg K8S_RELEASE=v1.21.3 \
+		--build-arg K8S_RELEASE=v1.24.2 \
 		--build-arg RESTY_CLI_VERSION=0.27 \
 		--build-arg RESTY_CLI_SHA=e5f4f3128af49ba5c4d039d0554e5ae91bbe05866f60eccfa96d3653274bff90 \
 		--build-arg LUAROCKS_VERSION=3.8.0 \
@@ -49,7 +49,7 @@ build: ensure-buildx
 		--build-arg CHART_TESTING_VERSION=3.0.0 \
 		--build-arg YAML_LINT_VERSION=1.13.0 \
 		--build-arg YAMALE_VERSION=1.8.0 \
-		--build-arg HELM_VERSION=v3.4.2 \
+		--build-arg HELM_VERSION=v3.9.0 \
 		-t $(IMAGE):$(TAG) rootfs
 
 # push the cross built image
diff --git a/internal/ingress/controller/checker_test.go b/internal/ingress/controller/checker_test.go
index fd712b6a8..5d9fb1baf 100644
--- a/internal/ingress/controller/checker_test.go
+++ b/internal/ingress/controller/checker_test.go
@@ -26,9 +26,9 @@ import (
 
 	"k8s.io/apiserver/pkg/server/healthz"
 
-	"k8s.io/ingress-nginx/internal/file"
 	ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
 	"k8s.io/ingress-nginx/internal/nginx"
+	"k8s.io/ingress-nginx/pkg/util/file"
 )
 
 func TestNginxCheck(t *testing.T) {
diff --git a/internal/ingress/controller/controller_test.go b/internal/ingress/controller/controller_test.go
index 398e18830..f875632e0 100644
--- a/internal/ingress/controller/controller_test.go
+++ b/internal/ingress/controller/controller_test.go
@@ -39,8 +39,8 @@ import (
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/kubernetes/fake"
 
-	"k8s.io/ingress-nginx/internal/file"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
+
 	"k8s.io/ingress-nginx/internal/ingress/annotations"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
@@ -56,6 +56,8 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
 	"k8s.io/ingress-nginx/internal/k8s"
 	"k8s.io/ingress-nginx/internal/net/ssl"
+
+	"k8s.io/ingress-nginx/pkg/util/file"
 )
 
 type fakeIngressStore struct {
diff --git a/internal/ingress/controller/endpoints_test.go b/internal/ingress/controller/endpoints_test.go
index 83c8e1837..f38ffabe7 100644
--- a/internal/ingress/controller/endpoints_test.go
+++ b/internal/ingress/controller/endpoints_test.go
@@ -22,7 +22,7 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 func TestGetEndpoints(t *testing.T) {
diff --git a/internal/ingress/controller/nginx_test.go b/internal/ingress/controller/nginx_test.go
index d168efffd..c2c71b797 100644
--- a/internal/ingress/controller/nginx_test.go
+++ b/internal/ingress/controller/nginx_test.go
@@ -32,8 +32,8 @@ import (
 	apiv1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/util/wait"
 
-	"k8s.io/ingress-nginx/internal/ingress"
 	"k8s.io/ingress-nginx/internal/nginx"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 func TestIsDynamicConfigurationEnough(t *testing.T) {
diff --git a/internal/ingress/controller/store/store_test.go b/internal/ingress/controller/store/store_test.go
index 4868f792f..9b8947f9c 100644
--- a/internal/ingress/controller/store/store_test.go
+++ b/internal/ingress/controller/store/store_test.go
@@ -36,9 +36,9 @@ import (
 	"k8s.io/client-go/tools/cache"
 	"sigs.k8s.io/controller-runtime/pkg/envtest"
 
-	"k8s.io/ingress-nginx/internal/ingress"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	"k8s.io/ingress-nginx/internal/ingress/controller/ingressclass"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 	"k8s.io/ingress-nginx/test/e2e/framework"
 )
 
diff --git a/internal/ingress/controller/template/template_test.go b/internal/ingress/controller/template/template_test.go
index a741919ed..cb1ebd1b7 100644
--- a/internal/ingress/controller/template/template_test.go
+++ b/internal/ingress/controller/template/template_test.go
@@ -34,7 +34,6 @@ import (
 	networking "k8s.io/api/networking/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	"k8s.io/ingress-nginx/internal/ingress"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
@@ -43,6 +42,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
 	"k8s.io/ingress-nginx/internal/ingress/controller/config"
 	"k8s.io/ingress-nginx/internal/nginx"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 func init() {
diff --git a/internal/ingress/metric/collectors/controller_test.go b/internal/ingress/metric/collectors/controller_test.go
index bc9f21c60..ef80bc96a 100644
--- a/internal/ingress/metric/collectors/controller_test.go
+++ b/internal/ingress/metric/collectors/controller_test.go
@@ -24,7 +24,7 @@ import (
 	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
-	"k8s.io/ingress-nginx/internal/ingress"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 func TestControllerCounters(t *testing.T) {
diff --git a/internal/ingress/status/status_test.go b/internal/ingress/status/status_test.go
index fefca5ff2..d4ef09e7c 100644
--- a/internal/ingress/status/status_test.go
+++ b/internal/ingress/status/status_test.go
@@ -28,10 +28,10 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	testclient "k8s.io/client-go/kubernetes/fake"
 
-	"k8s.io/ingress-nginx/internal/ingress"
 	"k8s.io/ingress-nginx/internal/ingress/controller/ingressclass"
 	"k8s.io/ingress-nginx/internal/k8s"
 	"k8s.io/ingress-nginx/internal/task"
+	"k8s.io/ingress-nginx/pkg/apis/ingress"
 )
 
 func buildLoadBalancerIngressByIP() []apiv1.LoadBalancerIngress {
diff --git a/internal/net/dns/dns_test.go b/internal/net/dns/dns_test.go
index 2b21e81e7..708e3c6df 100644
--- a/internal/net/dns/dns_test.go
+++ b/internal/net/dns/dns_test.go
@@ -21,7 +21,7 @@ import (
 	"os"
 	"testing"
 
-	"k8s.io/ingress-nginx/internal/file"
+	"k8s.io/ingress-nginx/pkg/util/file"
 )
 
 func TestGetDNSServers(t *testing.T) {
diff --git a/internal/net/ssl/ssl_test.go b/internal/net/ssl/ssl_test.go
index 0b972d21c..e251d01d1 100644
--- a/internal/net/ssl/ssl_test.go
+++ b/internal/net/ssl/ssl_test.go
@@ -39,7 +39,7 @@ import (
 	"time"
 
 	certutil "k8s.io/client-go/util/cert"
-	"k8s.io/ingress-nginx/internal/file"
+	"k8s.io/ingress-nginx/pkg/util/file"
 )
 
 // generateRSACerts generates a self signed certificate using a self generated ca
diff --git a/internal/ingress/types_equals_test.go b/pkg/apis/ingress/types_equals_test.go
similarity index 96%
rename from internal/ingress/types_equals_test.go
rename to pkg/apis/ingress/types_equals_test.go
index 8119d26c7..78d29d46c 100644
--- a/internal/ingress/types_equals_test.go
+++ b/pkg/apis/ingress/types_equals_test.go
@@ -25,19 +25,19 @@ import (
 )
 
 func TestEqualConfiguration(t *testing.T) {
-	ap, _ := filepath.Abs("../../test/manifests/configuration-a.json")
+	ap, _ := filepath.Abs("../../../test/manifests/configuration-a.json")
 	a, err := readJSON(ap)
 	if err != nil {
 		t.Errorf("unexpected error reading JSON file: %v", err)
 	}
 
-	bp, _ := filepath.Abs("../../test/manifests/configuration-b.json")
+	bp, _ := filepath.Abs("../../../test/manifests/configuration-b.json")
 	b, err := readJSON(bp)
 	if err != nil {
 		t.Errorf("unexpected error reading JSON file: %v", err)
 	}
 
-	cp, _ := filepath.Abs("../../test/manifests/configuration-c.json")
+	cp, _ := filepath.Abs("../../../test/manifests/configuration-c.json")
 	c, err := readJSON(cp)
 	if err != nil {
 		t.Errorf("unexpected error reading JSON file: %v", err)
diff --git a/cmd/nginx/flags_test.go b/pkg/flags/flags_test.go
similarity index 76%
rename from cmd/nginx/flags_test.go
rename to pkg/flags/flags_test.go
index b25fa7557..2a33d73dd 100644
--- a/cmd/nginx/flags_test.go
+++ b/pkg/flags/flags_test.go
@@ -14,32 +14,22 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package main
+package flags
 
 import (
-	"flag"
 	"os"
 	"testing"
 )
 
-// resetForTesting clears all flag state and sets the usage function as directed.
-// After calling resetForTesting, parse errors in flag handling will not
-// exit the program.
-// Extracted from https://github.com/golang/go/blob/master/src/flag/export_test.go
-func resetForTesting(usage func()) {
-	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
-	flag.Usage = usage
-}
-
 func TestNoMandatoryFlag(t *testing.T) {
-	_, _, err := parseFlags()
+	_, _, err := ParseFlags()
 	if err != nil {
 		t.Fatalf("Expected no error but got: %s", err)
 	}
 }
 
 func TestDefaults(t *testing.T) {
-	resetForTesting(func() { t.Fatal("Parsing failed") })
+	ResetForTesting(func() { t.Fatal("Parsing failed") })
 
 	oldArgs := os.Args
 	defer func() { os.Args = oldArgs }()
@@ -49,7 +39,7 @@ func TestDefaults(t *testing.T) {
 		"--https-port", "0",
 	}
 
-	showVersion, conf, err := parseFlags()
+	showVersion, conf, err := ParseFlags()
 	if err != nil {
 		t.Fatalf("Unexpected error parsing default flags: %v", err)
 	}
@@ -68,52 +58,52 @@ func TestSetupSSLProxy(t *testing.T) {
 }
 
 func TestFlagConflict(t *testing.T) {
-	resetForTesting(func() { t.Fatal("Parsing failed") })
+	ResetForTesting(func() { t.Fatal("Parsing failed") })
 
 	oldArgs := os.Args
 	defer func() { os.Args = oldArgs }()
 	os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--publish-status-address", "1.1.1.1"}
 
-	_, _, err := parseFlags()
+	_, _, err := ParseFlags()
 	if err == nil {
 		t.Fatalf("Expected an error parsing flags but none returned")
 	}
 }
 
 func TestMaxmindEdition(t *testing.T) {
-	resetForTesting(func() { t.Fatal("Parsing failed") })
+	ResetForTesting(func() { t.Fatal("Parsing failed") })
 
 	oldArgs := os.Args
 	defer func() { os.Args = oldArgs }()
 	os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--maxmind-license-key", "0000000", "--maxmind-edition-ids", "GeoLite2-City, TestCheck"}
 
-	_, _, err := parseFlags()
+	_, _, err := ParseFlags()
 	if err == nil {
 		t.Fatalf("Expected an error parsing flags but none returned")
 	}
 }
 
 func TestMaxmindMirror(t *testing.T) {
-	resetForTesting(func() { t.Fatal("Parsing failed") })
+	ResetForTesting(func() { t.Fatal("Parsing failed") })
 
 	oldArgs := os.Args
 	defer func() { os.Args = oldArgs }()
 	os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--maxmind-mirror", "http://geoip.local", "--maxmind-license-key", "0000000", "--maxmind-edition-ids", "GeoLite2-City, TestCheck"}
 
-	_, _, err := parseFlags()
+	_, _, err := ParseFlags()
 	if err == nil {
 		t.Fatalf("Expected an error parsing flags but none returned")
 	}
 }
 
 func TestMaxmindRetryDownload(t *testing.T) {
-	resetForTesting(func() { t.Fatal("Parsing failed") })
+	ResetForTesting(func() { t.Fatal("Parsing failed") })
 
 	oldArgs := os.Args
 	defer func() { os.Args = oldArgs }()
 	os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--maxmind-mirror", "http://127.0.0.1", "--maxmind-license-key", "0000000", "--maxmind-edition-ids", "GeoLite2-City", "--maxmind-retries-timeout", "1s", "--maxmind-retries-count", "3"}
 
-	_, _, err := parseFlags()
+	_, _, err := ParseFlags()
 	if err == nil {
 		t.Fatalf("Expected an error parsing flags but none returned")
 	}
diff --git a/internal/watch/file_watcher_test.go b/pkg/util/file/file_watcher_test.go
similarity index 96%
rename from internal/watch/file_watcher_test.go
rename to pkg/util/file/file_watcher_test.go
index d97d6b9ae..316cb6f1e 100644
--- a/internal/watch/file_watcher_test.go
+++ b/pkg/util/file/file_watcher_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package watch
+package file
 
 import (
 	"os"
@@ -22,8 +22,6 @@ import (
 	"path/filepath"
 	"testing"
 	"time"
-
-	"k8s.io/ingress-nginx/internal/file"
 )
 
 func prepareTimeout() chan bool {
@@ -61,7 +59,7 @@ func TestFileWatcher(t *testing.T) {
 		t.Fatalf("expected no events before writing a file")
 	case <-timeoutChan:
 	}
-	os.WriteFile(f.Name(), []byte{}, file.ReadWriteByUser)
+	os.WriteFile(f.Name(), []byte{}, ReadWriteByUser)
 	select {
 	case <-events:
 	case <-timeoutChan:
diff --git a/pkg/util/process/sigterm_test.go b/pkg/util/process/sigterm_test.go
new file mode 100644
index 000000000..2c2a6ee91
--- /dev/null
+++ b/pkg/util/process/sigterm_test.go
@@ -0,0 +1,79 @@
+/*
+Copyright 2022 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.
+*/
+
+package process
+
+import (
+	"fmt"
+	"syscall"
+	"testing"
+	"time"
+)
+
+type FakeProcess struct {
+	shouldError bool
+	exitCode    int
+}
+
+func (f *FakeProcess) Start() {
+}
+
+func (f *FakeProcess) Stop() error {
+	if f.shouldError {
+		return fmt.Errorf("error")
+	}
+	return nil
+}
+
+func (f *FakeProcess) exiterFunc(code int) {
+	f.exitCode = code
+}
+
+func sendDelayedSignal(delay time.Duration) {
+	time.Sleep(delay * time.Second)
+	syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
+}
+
+func TestHandleSigterm(t *testing.T) {
+	tests := []struct {
+		name        string
+		shouldError bool
+		delay       int
+	}{
+		{
+			name:        "should exit without error",
+			shouldError: false,
+		},
+		{
+			name:        "should exit with error",
+			shouldError: true,
+			delay:       2,
+		},
+	}
+	for _, tt := range tests {
+		process := &FakeProcess{shouldError: tt.shouldError}
+		t.Run(tt.name, func(t *testing.T) {
+			go sendDelayedSignal(2) // Send a signal after 2 seconds
+			HandleSigterm(process, tt.delay, process.exiterFunc)
+		})
+		if tt.shouldError && process.exitCode != 1 {
+			t.Errorf("wrong return, should be 1 and returned %d", process.exitCode)
+		}
+		if !tt.shouldError && process.exitCode != 0 {
+			t.Errorf("wrong return, should be 0 and returned %d", process.exitCode)
+		}
+	}
+}
diff --git a/test/e2e-image/Dockerfile b/test/e2e-image/Dockerfile
index e29c7524b..d88338d65 100644
--- a/test/e2e-image/Dockerfile
+++ b/test/e2e-image/Dockerfile
@@ -1,7 +1,7 @@
 ARG E2E_BASE_IMAGE
 FROM ${E2E_BASE_IMAGE} AS BASE
 
-FROM alpine:3.16.0
+FROM alpine:3.16.1
 
 RUN apk add -U --no-cache \
   ca-certificates \
diff --git a/test/e2e/run-chart-test.sh b/test/e2e/run-chart-test.sh
index 49ec146a7..2af8aa1d3 100755
--- a/test/e2e/run-chart-test.sh
+++ b/test/e2e/run-chart-test.sh
@@ -78,7 +78,7 @@ fi
 
 if [ "${SKIP_IMAGE_CREATION:-false}" = "false" ]; then
   if ! command -v ginkgo &> /dev/null; then
-    go get github.com/onsi/ginkgo/ginkgo@v1.16.4
+    go get github.com/onsi/ginkgo/v2/ginkgo@v2.1.4
   fi
   echo "[dev-env] building image"
   make -C ${DIR}/../../ clean-image build image
diff --git a/test/e2e/run.sh b/test/e2e/run.sh
index 6edc18848..6b1cabb4c 100755
--- a/test/e2e/run.sh
+++ b/test/e2e/run.sh
@@ -79,7 +79,7 @@ fi
 
 if [ "${SKIP_IMAGE_CREATION:-false}" = "false" ]; then
   if ! command -v ginkgo &> /dev/null; then
-    go get github.com/onsi/ginkgo/ginkgo@v1.16.4
+    go get github.com/onsi/ginkgo/v2/ginkgo@v2.1.4
   fi
 
   echo "[dev-env] building image"