diff --git a/Makefile b/Makefile index fc8c34f0f..955dabbe6 100644 --- a/Makefile +++ b/Makefile @@ -105,7 +105,7 @@ container: clean-container .container-$(ARCH) @echo "+ Copying artifact to temporary directory" mkdir -p $(TEMP_DIR)/rootfs cp bin/$(ARCH)/nginx-ingress-controller $(TEMP_DIR)/rootfs/nginx-ingress-controller - + cp bin/$(ARCH)/dbg $(TEMP_DIR)/rootfs/dbg @echo "+ Building container image $(MULTI_ARCH_IMG):$(TAG)" cp -RP ./* $(TEMP_DIR) $(SED_I) "s|BASEIMAGE|$(BASEIMAGE)|g" $(DOCKERFILE) diff --git a/build/build.sh b/build/build.sh index 6d2e39cbd..0fd952a11 100755 --- a/build/build.sh +++ b/build/build.sh @@ -48,3 +48,11 @@ go build \ -X ${PKG}/version.COMMIT=${GIT_COMMIT} \ -X ${PKG}/version.REPO=${REPO_INFO}" \ -o bin/${ARCH}/nginx-ingress-controller ${PKG}/cmd/nginx + +go build \ + ${GOBUILD_FLAGS} \ + -ldflags "-s -w \ + -X ${PKG}/version.RELEASE=${TAG} \ + -X ${PKG}/version.COMMIT=${GIT_COMMIT} \ + -X ${PKG}/version.REPO=${REPO_INFO}" \ + -o bin/${ARCH}/dbg ${PKG}/cmd/dbg diff --git a/cmd/dbg/main.go b/cmd/dbg/main.go new file mode 100644 index 000000000..401fb6b52 --- /dev/null +++ b/cmd/dbg/main.go @@ -0,0 +1,203 @@ +/* +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. +*/ + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/spf13/cobra" + "k8s.io/ingress-nginx/internal/nginx" + "os" +) + +const ( + backendsPath = "/configuration/backends" + generalPath = "/configuration/general" +) + +func main() { + rootCmd := &cobra.Command{ + Use: "dbg", + Short: "dbg is a tool for quickly inspecting the state of the nginx instance", + } + + backendsCmd := &cobra.Command{ + Use: "backends", + Short: "Inspect the dynamically-loaded backends information", + } + rootCmd.AddCommand(backendsCmd) + + backendsAllCmd := &cobra.Command{ + Use: "all", + Short: "Output the all dynamic backend information as a JSON array", + Run: func(cmd *cobra.Command, args []string) { + backendsAll() + }, + } + backendsCmd.AddCommand(backendsAllCmd) + + backendsListCmd := &cobra.Command{ + Use: "list", + Short: "Output a newline-separated list of the backend names", + Run: func(cmd *cobra.Command, args []string) { + backendsList() + }, + } + backendsCmd.AddCommand(backendsListCmd) + + backendsGetCmd := &cobra.Command{ + Use: "get [backend name]", + Short: "Output the backend information only for the backend that has this name", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + backendsGet(args[0]) + }, + } + backendsCmd.AddCommand(backendsGetCmd) + + generalCmd := &cobra.Command{ + Use: "general", + Short: "Output the general dynamic lua state", + Run: func(cmd *cobra.Command, args []string) { + general() + }, + } + rootCmd.AddCommand(generalCmd) + + confCmd := &cobra.Command{ + Use: "conf", + Short: "Dump the contents of /etc/nginx/nginx.conf", + Run: func(cmd *cobra.Command, args []string) { + readNginxConf() + }, + } + rootCmd.AddCommand(confCmd) + + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } + +} + +func backendsAll() { + statusCode, body, requestErr := nginx.NewGetStatusRequest(backendsPath) + if requestErr != nil { + fmt.Println(requestErr) + return + } + if statusCode != 200 { + fmt.Printf("Nginx returned code %v", statusCode) + return + } + + var prettyBuffer bytes.Buffer + indentErr := json.Indent(&prettyBuffer, body, "", " ") + if indentErr != nil { + fmt.Println(indentErr) + return + } + + fmt.Println(string(prettyBuffer.Bytes())) +} + +func backendsList() { + statusCode, body, requestErr := nginx.NewGetStatusRequest(backendsPath) + if requestErr != nil { + fmt.Println(requestErr) + return + } + if statusCode != 200 { + fmt.Printf("Nginx returned code %v", statusCode) + return + } + + var f interface{} + unmarshalErr := json.Unmarshal(body, &f) + if unmarshalErr != nil { + fmt.Println(unmarshalErr) + return + } + backends := f.([]interface{}) + + for _, backendi := range backends { + backend := backendi.(map[string]interface{}) + fmt.Println(backend["name"].(string)) + } +} + +func backendsGet(name string) { + statusCode, body, requestErr := nginx.NewGetStatusRequest(backendsPath) + if requestErr != nil { + fmt.Println(requestErr) + return + } + if statusCode != 200 { + fmt.Printf("Nginx returned code %v", statusCode) + return + } + + var f interface{} + unmarshalErr := json.Unmarshal(body, &f) + if unmarshalErr != nil { + fmt.Println(unmarshalErr) + return + } + backends := f.([]interface{}) + + for _, backendi := range backends { + backend := backendi.(map[string]interface{}) + if backend["name"].(string) == name { + printed, _ := json.MarshalIndent(backend, "", " ") + fmt.Println(string(printed)) + return + } + } + fmt.Println("A backend of this name was not found.") +} + +func general() { + statusCode, body, requestErr := nginx.NewGetStatusRequest(generalPath) + if requestErr != nil { + fmt.Println(requestErr) + return + } + if statusCode != 200 { + fmt.Printf("Nginx returned code %v", statusCode) + return + } + + var prettyBuffer bytes.Buffer + indentErr := json.Indent(&prettyBuffer, body, "", " ") + if indentErr != nil { + fmt.Println(indentErr) + return + } + + fmt.Println(string(prettyBuffer.Bytes())) +} + +func readNginxConf() { + conf, err := nginx.ReadNginxConf() + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(conf) +} diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 7e6784c95..5792d5eaa 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -93,6 +93,87 @@ kube-system kube-dns ClusterIP 10.96.0.10 kube-system kubernetes-dashboard NodePort 10.103.128.17 80:30000/TCP 30m ``` +Use the `/dbg` Tool to Check Dynamic Configuration + +```console +$ kubectl exec -n nginx-ingress-controller-67956bf89d-fv58j /dbg +dbg is a tool for quickly inspecting the state of the nginx instance + +Usage: + dbg [command] + +Available Commands: + backends Inspect the dynamically-loaded backends information + conf Dump the contents of /etc/nginx/nginx.conf + general Output the general dynamic lua state + help Help about any command + +Flags: + -h, --help help for dbg + +Use "dbg [command] --help" for more information about a command. + +``` + +```console +$ kubectl exec -n nginx-ingress-controller-67956bf89d-fv58j /dbg backends +Inspect the dynamically-loaded backends information. + +Usage: + dbg backends [command] + +Available Commands: + all Output the all dynamic backend information as a JSON array + get Output the backend information only for the backend that has this name + list Output a newline-separated list of the backend names + +Flags: + -h, --help help for backends + +Use "dbg backends [command] --help" for more information about a command. +``` + +```console +$ kubectl exec -n nginx-ingress-controller-67956bf89d-fv58j /dbg backends list +coffee-svc-80 +tea-svc-80 +upstream-default-backend +``` + +```console +$ kubectl exec -n nginx-ingress-controller-67956bf89d-fv58j /dbg backends get coffee-svc-80 +{ + "endpoints": [ + { + "address": "10.1.1.112", + "port": "8080" + }, + { + "address": "10.1.1.119", + "port": "8080" + }, + { + "address": "10.1.1.121", + "port": "8080" + } + ], + "load-balance": "ewma", + "name": "coffee-svc-80", + "noServer": false, + "port": 0, + "secureCACert": { + "caFilename": "", + "pemSha": "", + "secret": "" + }, + "service": { + "metadata": { + "creationTimestamp": null + }, + "spec": { +.... +``` + ## Debug Logging Using the flag `--v=XX` it is possible to increase the level of logging. This is performed by editing diff --git a/internal/nginx/main.go b/internal/nginx/main.go index 474bf9fb5..5d90e87a5 100644 --- a/internal/nginx/main.go +++ b/internal/nginx/main.go @@ -22,6 +22,7 @@ import ( "fmt" "io/ioutil" "net/http" + "os" "time" "github.com/tv42/httpunix" @@ -87,6 +88,21 @@ func NewPostStatusRequest(path, contentType string, data interface{}) (int, []by return res.StatusCode, body, nil } +// ReadNginxConf reads the nginx configuration file into a string +func ReadNginxConf() (string, error) { + confFile, err := os.Open("/etc/nginx/nginx.conf") + if err != nil { + return "", err + } + defer confFile.Close() + + contents, err := ioutil.ReadAll(confFile) + if err != nil { + return "", err + } + return string(contents), nil +} + func buildUnixSocketClient() *http.Client { u := &httpunix.Transport{ DialTimeout: 1 * time.Second, diff --git a/test/e2e/dbg/main.go b/test/e2e/dbg/main.go new file mode 100644 index 000000000..2d252440e --- /dev/null +++ b/test/e2e/dbg/main.go @@ -0,0 +1,102 @@ +/* +Copyright 2018 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 dbg + +import ( + "encoding/json" + "strings" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "k8s.io/ingress-nginx/test/e2e/framework" +) + +var _ = framework.IngressNginxDescribe("Debug Tool", func() { + f := framework.NewDefaultFramework("debug-tool") + host := "foo.com" + + BeforeEach(func() { + f.NewEchoDeploymentWithReplicas(1) + }) + + AfterEach(func() { + }) + + It("should list the backend servers", func() { + annotations := map[string]string{} + + ing := framework.NewSingleIngress(host, "/", host, f.IngressController.Namespace, "http-svc", 80, &annotations) + f.EnsureIngress(ing) + + f.WaitForNginxConfiguration(func(cfg string) bool { + return Expect(cfg).Should(ContainSubstring(host)) + }) + + cmd := "/dbg backends list" + output, err := f.ExecIngressPod(cmd) + Expect(err).Should(BeNil()) + + // Should be 2: the default and the echo deployment + numUpstreams := len(strings.Split(strings.Trim(string(output), "\n"), "\n")) + Expect(numUpstreams).Should(Equal(2)) + + }) + + It("should get information for a specific backend server", func() { + annotations := map[string]string{} + + ing := framework.NewSingleIngress(host, "/", host, f.IngressController.Namespace, "http-svc", 80, &annotations) + f.EnsureIngress(ing) + + f.WaitForNginxConfiguration(func(cfg string) bool { + return Expect(cfg).Should(ContainSubstring(host)) + }) + + cmd := "/dbg backends list" + output, err := f.ExecIngressPod(cmd) + Expect(err).Should(BeNil()) + + backends := strings.Split(string(output), "\n") + Expect(len(backends)).Should(BeNumerically(">", 0)) + + getCmd := "/dbg backends get " + backends[0] + output, err = f.ExecIngressPod(getCmd) + + var f map[string]interface{} + unmarshalErr := json.Unmarshal([]byte(output), &f) + Expect(unmarshalErr).Should(BeNil()) + + // Check that the backend we've gotten has the same name as the one we requested + Expect(backends[0]).Should(Equal(f["name"].(string))) + }) + + It("should produce valid JSON for /dbg general", func() { + annotations := map[string]string{} + + ing := framework.NewSingleIngress(host, "/", host, f.IngressController.Namespace, "http-svc", 80, &annotations) + f.EnsureIngress(ing) + + cmd := "/dbg general" + output, err := f.ExecIngressPod(cmd) + Expect(err).Should(BeNil()) + + var f interface{} + unmarshalErr := json.Unmarshal([]byte(output), &f) + Expect(unmarshalErr).Should(BeNil()) + }) +}) diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go index d65428bb1..215371f13 100644 --- a/test/e2e/e2e.go +++ b/test/e2e/e2e.go @@ -32,6 +32,7 @@ import ( // tests to run _ "k8s.io/ingress-nginx/test/e2e/annotations" + _ "k8s.io/ingress-nginx/test/e2e/dbg" _ "k8s.io/ingress-nginx/test/e2e/defaultbackend" _ "k8s.io/ingress-nginx/test/e2e/gracefulshutdown" _ "k8s.io/ingress-nginx/test/e2e/loadbalance"