github.com/zhyoulun/cilium@v1.6.12/test/k8sT/istio.go (about) 1 // Copyright 2018-2019 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package k8sTest 16 17 import ( 18 "context" 19 "fmt" 20 "runtime" 21 "time" 22 23 . "github.com/cilium/cilium/test/ginkgo-ext" 24 "github.com/cilium/cilium/test/helpers" 25 26 . "github.com/onsi/gomega" 27 ) 28 29 // This tests the Istio integration, following the configuration 30 // instructions specified in the Istio Getting Started Guide in 31 // Documentation/gettingstarted/istio.rst. 32 var _ = Describe("K8sIstioTest", func() { 33 34 var ( 35 // istioSystemNamespace is the default namespace into which Istio is 36 // installed. 37 istioSystemNamespace = "istio-system" 38 39 istioVersion = "1.5.9" 40 41 // Modifiers for pre-release testing, normally empty 42 prerelease = "" // "-beta.1" 43 istioctlParams = "" 44 // Keeping these here in comments serve multiple purposes: 45 // - remind how to test with prerelease images in future 46 // - cause CI infra to prepull these images so that they do not 47 // need to be pulled on demand during the test 48 // " --set values.pilot.image=docker.io/cilium/istio_pilot:1.5.9" + 49 // " --set values.global.proxy.image=docker.io/cilium/istio_proxy:1.5.9" + 50 // " --set values.global.proxy_init.image=docker.io/cilium/istio_proxy:1.5.9" 51 52 // Map of tested runtimes for cilium-istioctl 53 ciliumIstioctlOSes = map[string]string{ 54 "darwin": "osx", 55 "linux": "linux", 56 } 57 58 // istioServiceNames is the set of Istio services needed for the tests 59 istioServiceNames = []string{ 60 "istio-ingressgateway", 61 "istio-pilot", 62 } 63 64 // wgetCommand is the command used in this test because the Istio apps 65 // do not provide curl. 66 wgetCommand = fmt.Sprintf("wget --tries=2 --connect-timeout %d", helpers.CurlConnectTimeout) 67 68 kubectl *helpers.Kubectl 69 microscopeCancel = func() error { return nil } 70 uptimeCancel context.CancelFunc 71 72 teardownTimeout = 10 * time.Minute 73 ) 74 75 BeforeAll(func() { 76 k8sVersion := helpers.GetCurrentK8SEnv() 77 switch k8sVersion { 78 case "1.7", "1.8", "1.9", "1.10", "1.11", "1.12", "1.13": 79 Skip(fmt.Sprintf("Istio %s doesn't support K8S %s", istioVersion, k8sVersion)) 80 } 81 82 kubectl = helpers.CreateKubectl(helpers.K8s1VMName(), logger) 83 84 By("Downloading cilium-istioctl") 85 os := "linux" 86 if kubectl.IsLocal() { 87 // Use Ginkgo runtime OS instead when commands are executed in the local Ginkgo host 88 os = ciliumIstioctlOSes[runtime.GOOS] 89 } 90 ciliumIstioctlURL := "https://github.com/cilium/istio/releases/download/" + istioVersion + prerelease + "/cilium-istioctl-" + istioVersion + "-" + os + ".tar.gz" 91 res := kubectl.Exec(fmt.Sprintf("curl --retry 5 -L %s | tar xz", ciliumIstioctlURL)) 92 res.ExpectSuccess("unable to download %s", ciliumIstioctlURL) 93 res = kubectl.ExecShort("./cilium-istioctl version") 94 res.ExpectSuccess("unable to execute cilium-istioctl") 95 96 DeployCiliumAndDNS(kubectl) 97 98 By("Labeling default namespace for sidecar injection") 99 res = kubectl.NamespaceLabel(helpers.DefaultNamespace, "istio-injection=enabled") 100 res.ExpectSuccess("unable to label namespace %q", helpers.DefaultNamespace) 101 102 By("Deploying Istio") 103 res = kubectl.Exec("./cilium-istioctl manifest apply -y" + istioctlParams) 104 res.ExpectSuccess("unable to deploy Istio") 105 }) 106 107 AfterAll(func() { 108 By("Deleting default namespace sidecar injection label") 109 _ = kubectl.NamespaceLabel(helpers.DefaultNamespace, "istio-injection-") 110 111 By("Deleting the Istio resources") 112 _ = kubectl.Exec(fmt.Sprintf("./cilium-istioctl manifest generate | %s delete -f -", helpers.KubectlCmd)) 113 114 By("Waiting all terminating PODs to disappear") 115 err := kubectl.WaitCleanAllTerminatingPods(teardownTimeout) 116 ExpectWithOffset(1, err).To(BeNil(), "terminating Istio PODs are not deleted after timeout") 117 118 By("Deleting the istio-system namespace") 119 _ = kubectl.NamespaceDelete(istioSystemNamespace) 120 121 kubectl.CloseSSHClient() 122 }) 123 124 JustBeforeEach(func() { 125 var err error 126 err, microscopeCancel = kubectl.MicroscopeStart() 127 Expect(err).To(BeNil(), "Microscope cannot be started") 128 129 uptimeCancel, err = kubectl.BackgroundReport("uptime") 130 Expect(err).To(BeNil(), "Cannot start background report process") 131 }) 132 133 JustAfterEach(func() { 134 Expect(microscopeCancel()).To(BeNil(), "Cannot stop microscope") 135 uptimeCancel() 136 137 kubectl.ValidateNoErrorsInLogs(CurrentGinkgoTestDescription().Duration) 138 }) 139 140 AfterFailed(func() { 141 kubectl.CiliumReport(helpers.KubeSystemNamespace, 142 "cilium endpoint list", 143 "cilium bpf proxy list") 144 }) 145 146 // This is defined as a separate function to be called from the test below 147 // so that we properly capture test artifacts if any of the assertions fail 148 // (see https://github.com/cilium/cilium/pull/8508). 149 waitIstioReady := func() { 150 // Ignore one-time jobs and Prometheus. All other pods in the 151 // namespaces have an "istio" label. 152 By("Waiting for Istio pods to be ready") 153 // First wait for at least one POD to get into running state so that WaitforPods 154 // below does not succeed if there are no PODs with the "istio" label. 155 err := kubectl.WaitforNPodsRunning(istioSystemNamespace, "-l istio", 1, helpers.HelperTimeout) 156 ExpectWithOffset(1, err).To(BeNil(), 157 "No Istio POD is Running after timeout in namespace %q", istioSystemNamespace) 158 159 // Then wait for all the Istio PODs to get Ready 160 // Note that this succeeds if there are no PODs matching the filter (-l istio -n istio-system). 161 err = kubectl.WaitforPods(istioSystemNamespace, "-l istio", helpers.HelperTimeout) 162 ExpectWithOffset(1, err).To(BeNil(), 163 "Istio pods are not ready after timeout in namespace %q", istioSystemNamespace) 164 165 for _, name := range istioServiceNames { 166 By("Waiting for Istio service %q to be ready", name) 167 err = kubectl.WaitForServiceEndpoints( 168 istioSystemNamespace, "", name, helpers.HelperTimeout) 169 ExpectWithOffset(1, err).Should(BeNil(), "Service %q is not ready after timeout", name) 170 } 171 172 for _, name := range istioServiceNames { 173 By("Waiting for DNS to resolve Istio service %q", name) 174 err = kubectl.WaitForKubeDNSEntry(name, istioSystemNamespace) 175 ExpectWithOffset(1, err).To(BeNil(), "DNS entry is not ready after timeout") 176 } 177 } 178 179 // This is a subset of Services's "Bookinfo Demo" test suite, with the pods 180 // injected with Istio sidecar proxies and Istio mTLS enabled. 181 SkipContextIf(func() bool { return ciliumIstioctlOSes[runtime.GOOS] == "" }, "Istio Bookinfo Demo", func() { 182 183 var ( 184 resourceYAMLPaths []string 185 policyPaths []string 186 ) 187 188 AfterEach(func() { 189 for _, resourcePath := range resourceYAMLPaths { 190 By("Deleting resource in file %q", resourcePath) 191 // Explicitly do not check result to avoid having assertions in AfterEach. 192 _ = kubectl.Delete(resourcePath) 193 } 194 195 for _, policyPath := range policyPaths { 196 By("Deleting policy in file %q", policyPath) 197 // Explicitly do not check result to avoid having assertions in AfterEach. 198 _ = kubectl.Delete(policyPath) 199 } 200 }) 201 202 // shouldConnect checks that srcPod can connect to dstURI. 203 shouldConnect := func(srcPod, dstURI string) bool { 204 By("Checking that %q can connect to %q", srcPod, dstURI) 205 res := kubectl.ExecPodCmd( 206 helpers.DefaultNamespace, srcPod, fmt.Sprintf("%s %s", wgetCommand, dstURI)) 207 if !res.WasSuccessful() { 208 GinkgoPrint("Unable to connect from %q to %q: %s", srcPod, dstURI, res.OutputPrettyPrint()) 209 return false 210 } 211 return true 212 } 213 214 // shouldNotConnect checks that srcPod cannot connect to dstURI. 215 shouldNotConnect := func(srcPod, dstURI string) bool { 216 By("Checking that %q cannot connect to %q", srcPod, dstURI) 217 res := kubectl.ExecPodCmd( 218 helpers.DefaultNamespace, srcPod, fmt.Sprintf("%s %s", wgetCommand, dstURI)) 219 if res.WasSuccessful() { 220 GinkgoPrint("Was able to connect from %q to %q, but expected no connection: %s", srcPod, dstURI, res.OutputPrettyPrint()) 221 return false 222 } 223 return true 224 } 225 226 // formatLabelArgument formats the provided key-value pairs as labels for use in 227 // querying Kubernetes. 228 formatLabelArgument := func(firstKey, firstValue string, nextLabels ...string) string { 229 baseString := fmt.Sprintf("-l %s=%s", firstKey, firstValue) 230 if nextLabels == nil { 231 return baseString 232 } else if len(nextLabels)%2 != 0 { 233 Fail("must provide even number of arguments for label key-value pairings") 234 } else { 235 for i := 0; i < len(nextLabels); i += 2 { 236 baseString = fmt.Sprintf("%s,%s=%s", baseString, nextLabels[i], nextLabels[i+1]) 237 } 238 } 239 return baseString 240 } 241 242 // formatAPI is a helper function which formats a URI to access. 243 formatAPI := func(service, port, resource string) string { 244 target := fmt.Sprintf( 245 "%s.%s.svc.cluster.local:%s", 246 service, helpers.DefaultNamespace, port) 247 if resource != "" { 248 return fmt.Sprintf("%s/%s", target, resource) 249 } 250 return target 251 } 252 253 It("Tests bookinfo inter-service connectivity", func() { 254 var err error 255 version := "version" 256 v1 := "v1" 257 258 productPage := "productpage" 259 reviews := "reviews" 260 ratings := "ratings" 261 details := "details" 262 dnsChecks := []string{productPage, reviews, ratings, details} 263 app := "app" 264 health := "health" 265 ratingsPath := "ratings/0" 266 apiPort := "9080" 267 podNameFilter := "{.items[*].metadata.name}" 268 269 bookinfoV1YAML := helpers.ManifestGet("bookinfo-v1.yaml") 270 bookinfoV2YAML := helpers.ManifestGet("bookinfo-v2.yaml") 271 l7PolicyPath := helpers.ManifestGet("cnp-specs.yaml") 272 273 waitIstioReady() 274 275 // Create the L7 policy before creating the pods, in order to test 276 // that the sidecar proxy mode doesn't deadlock on endpoint 277 // creation in this case. 278 policyPaths = []string{l7PolicyPath} 279 for _, policyPath := range policyPaths { 280 By("Creating policy in file %q", policyPath) 281 _, err := kubectl.CiliumPolicyAction(helpers.DefaultNamespace, policyPath, helpers.KubectlApply, helpers.HelperTimeout) 282 Expect(err).Should(BeNil(), "Unable to create policy %q", policyPath) 283 } 284 285 resourceYAMLPaths = []string{bookinfoV2YAML, bookinfoV1YAML} 286 for _, resourcePath := range resourceYAMLPaths { 287 By("Creating resources in file %q", resourcePath) 288 res := kubectl.Create(resourcePath) 289 res.ExpectSuccess("Unable to create resource %q", resourcePath) 290 } 291 292 // Wait for pods and endpoints to be ready before creating the 293 // next resources to reduce the load on the next pod creations, 294 // in order to reduce the probability of regeneration timeout. 295 By("Waiting for Bookinfo pods to be ready") 296 err = kubectl.WaitforPods(helpers.DefaultNamespace, "-l zgroup=bookinfo", helpers.HelperTimeout) 297 Expect(err).Should(BeNil(), "Pods are not ready after timeout") 298 299 By("Waiting for Bookinfo endpoints to be ready") 300 err = kubectl.CiliumEndpointWaitReady() 301 Expect(err).Should(BeNil(), "Endpoints are not ready after timeout") 302 303 for _, service := range []string{details, ratings, reviews, productPage} { 304 By("Waiting for Bookinfo service %q to be ready", service) 305 err = kubectl.WaitForServiceEndpoints( 306 helpers.DefaultNamespace, "", service, 307 helpers.HelperTimeout) 308 Expect(err).Should(BeNil(), "Service %q is not ready after timeout", service) 309 } 310 311 for _, name := range dnsChecks { 312 By("Waiting for DNS to resolve Bookinfo service %q", name) 313 err = kubectl.WaitForKubeDNSEntry(name, helpers.DefaultNamespace) 314 Expect(err).To(BeNil(), "DNS entry is not ready after timeout") 315 } 316 317 By("Testing L7 filtering") 318 reviewsPodV1, err := kubectl.GetPods(helpers.DefaultNamespace, formatLabelArgument(app, reviews, version, v1)).Filter(podNameFilter) 319 Expect(err).Should(BeNil(), "Cannot get reviewsV1 pods") 320 productpagePodV1, err := kubectl.GetPods(helpers.DefaultNamespace, formatLabelArgument(app, productPage, version, v1)).Filter(podNameFilter) 321 Expect(err).Should(BeNil(), "Cannot get productpageV1 pods") 322 323 // Connectivity checks often need to be repeated because Pilot 324 // is eventually consistent, i.e. it may take some time for a 325 // sidecar proxy to get updated with the configuration for another 326 // new endpoint and it rejects egress traffic with 503s in the 327 // meantime. 328 err = helpers.WithTimeout(func() bool { 329 allGood := true 330 331 allGood = shouldConnect(reviewsPodV1.String(), formatAPI(ratings, apiPort, health)) && allGood 332 allGood = shouldNotConnect(reviewsPodV1.String(), formatAPI(ratings, apiPort, ratingsPath)) && allGood 333 334 allGood = shouldConnect(productpagePodV1.String(), formatAPI(details, apiPort, health)) && allGood 335 336 allGood = shouldNotConnect(productpagePodV1.String(), formatAPI(ratings, apiPort, health)) && allGood 337 allGood = shouldNotConnect(productpagePodV1.String(), formatAPI(ratings, apiPort, ratingsPath)) && allGood 338 339 return allGood 340 }, "Istio sidecar proxies are not configured", &helpers.TimeoutConfig{Timeout: helpers.HelperTimeout}) 341 Expect(err).Should(BeNil(), "Cannot configure Istio sidecar proxies") 342 }) 343 }) 344 })