├── .circleci └── config.yml ├── .gitignore ├── caller-service ├── k8s │ └── deployment.yaml ├── pom.xml ├── skaffold.yaml └── src │ ├── main │ ├── java │ │ └── pl │ │ │ └── piomin │ │ │ └── samples │ │ │ └── kubernetes │ │ │ ├── CallerApplication.java │ │ │ ├── controller │ │ │ └── CallerController.java │ │ │ └── utils │ │ │ └── AppVersion.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── pl │ └── piomin │ └── samples │ └── kubernetes │ └── CallerCallmeTests.java ├── callme-service ├── pom.xml └── src │ └── main │ ├── java │ └── pl │ │ └── piomin │ │ └── samples │ │ └── kubernetes │ │ ├── CallmeApplication.java │ │ ├── controller │ │ └── CallmeController.java │ │ └── utils │ │ └── AppVersion.java │ └── resources │ └── application.yml ├── pom.xml ├── readme.md └── renovate.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: 'cimg/openjdk:21.0.6' 7 | steps: 8 | - checkout 9 | - run: 10 | name: Analyze on SonarCloud 11 | command: mvn verify sonar:sonar 12 | 13 | executors: 14 | jdk: 15 | docker: 16 | - image: 'cimg/openjdk:21.0.6' 17 | 18 | orbs: 19 | maven: circleci/maven@2.0.0 20 | 21 | workflows: 22 | maven_test: 23 | jobs: 24 | - maven/test: 25 | executor: jdk 26 | - build: 27 | context: SonarCloud -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /caller-service/target/ 3 | /callme-service/target/ -------------------------------------------------------------------------------- /caller-service/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: caller-deployment-v1 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: caller 9 | version: v1 10 | template: 11 | metadata: 12 | labels: 13 | app: caller 14 | version: v1 15 | spec: 16 | containers: 17 | - name: caller 18 | image: /caller-service 19 | 20 | ports: 21 | - containerPort: 8080 22 | env: 23 | - name: POD_NAME 24 | valueFrom: 25 | fieldRef: 26 | fieldPath: metadata.name 27 | - name: POD_NAMESPACE 28 | valueFrom: 29 | fieldRef: 30 | fieldPath: metadata.namespace 31 | volumeMounts: 32 | - mountPath: /etc/podinfo 33 | name: podinfo 34 | volumes: 35 | - name: podinfo 36 | downwardAPI: 37 | items: 38 | - path: "labels" 39 | fieldRef: 40 | fieldPath: metadata.labels 41 | --- 42 | apiVersion: v1 43 | kind: Service 44 | metadata: 45 | name: caller-service 46 | spec: 47 | type: ClusterIP 48 | selector: 49 | app: caller 50 | ports: 51 | - port: 8080 -------------------------------------------------------------------------------- /caller-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | pl.piomin 9 | kubernetes-workshop 10 | 1.0 11 | 12 | caller-service 13 | 1.0 14 | 15 | 16 | ${project.artifactId} 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-actuator 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jpa 31 | 32 | 33 | com.h2database 34 | h2 35 | runtime 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | test 41 | 42 | 43 | io.specto 44 | hoverfly-java-junit5 45 | 0.20.2 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-maven-plugin 55 | 56 | 57 | com.google.cloud.tools 58 | jib-maven-plugin 59 | 3.4.5 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /caller-service/skaffold.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: skaffold/v2beta5 2 | kind: Config 3 | metadata: 4 | name: caller-service 5 | build: 6 | artifacts: 7 | - image: piomin/caller-service 8 | jib: {} 9 | -------------------------------------------------------------------------------- /caller-service/src/main/java/pl/piomin/samples/kubernetes/CallerApplication.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.kubernetes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.web.client.RestTemplateBuilder; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | @SpringBootApplication 10 | public class CallerApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(CallerApplication.class, args); 14 | } 15 | 16 | @Bean 17 | RestTemplate restTemplate() { 18 | return new RestTemplateBuilder() 19 | .build(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /caller-service/src/main/java/pl/piomin/samples/kubernetes/controller/CallerController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.kubernetes.controller; 2 | 3 | import java.util.List; 4 | 5 | import pl.piomin.samples.kubernetes.utils.AppVersion; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.http.HttpEntity; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.util.LinkedMultiValueMap; 13 | import org.springframework.util.MultiValueMap; 14 | import org.springframework.web.bind.annotation.GetMapping; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RestController; 17 | import org.springframework.web.client.RestTemplate; 18 | 19 | @RestController 20 | @RequestMapping("/caller") 21 | public class CallerController { 22 | 23 | private RestTemplate restTemplate; 24 | 25 | CallerController(RestTemplate restTemplate) { 26 | this.restTemplate = restTemplate; 27 | } 28 | 29 | @Value("${spring.application.name}") 30 | private String appName; 31 | @Value("${POD_NAME}") 32 | private String podName; 33 | @Value("${POD_NAMESPACE}") 34 | private String podNamespace; 35 | 36 | @Autowired 37 | private AppVersion appVersion; 38 | 39 | @GetMapping("/ping") 40 | public String ping() { 41 | return appName + "(" + appVersion.getVersionLabel() + "): " + podName + " in " + podNamespace; 42 | } 43 | 44 | private String callme(String version) { 45 | HttpEntity httpEntity = null; 46 | if (version != null) { 47 | MultiValueMap map = new LinkedMultiValueMap<>(); 48 | map.put("X-Version", List.of(version)); 49 | httpEntity = new HttpEntity(map); 50 | } 51 | ResponseEntity entity = restTemplate 52 | .exchange("http://callme-service:8080/callme/ping", HttpMethod.GET, httpEntity, String.class); 53 | return entity.getBody(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /caller-service/src/main/java/pl/piomin/samples/kubernetes/utils/AppVersion.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.kubernetes.utils; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Paths; 6 | import java.util.Optional; 7 | import java.util.stream.Stream; 8 | 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class AppVersion { 13 | 14 | public String getVersionLabel() { 15 | try (Stream stream = Files.lines(Paths.get("/etc/podinfo/labels"))) { 16 | Optional optVersion = stream.filter(it -> it.startsWith("version=")).findFirst(); 17 | return optVersion.map(s -> s.split("=")[1].replace("\"", "")) 18 | .orElse("null"); 19 | } catch (IOException e) { 20 | e.printStackTrace(); 21 | } 22 | return "null"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /caller-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.application.name: caller-service -------------------------------------------------------------------------------- /caller-service/src/test/java/pl/piomin/samples/kubernetes/CallerCallmeTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.kubernetes; 2 | import io.specto.hoverfly.junit.core.Hoverfly; 3 | import io.specto.hoverfly.junit5.HoverflyExtension; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.boot.test.web.client.TestRestTemplate; 9 | 10 | import static io.specto.hoverfly.junit.core.SimulationSource.dsl; 11 | import static io.specto.hoverfly.junit.dsl.HoverflyDsl.service; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static io.specto.hoverfly.junit.dsl.ResponseCreators.success; 14 | 15 | @SpringBootTest(properties = {"VERSION = v2", "POD_NAME = test", "POD_NAMESPACE = default"}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 16 | @ExtendWith(HoverflyExtension.class) 17 | public class CallerCallmeTests { 18 | 19 | @Autowired 20 | TestRestTemplate restTemplate; 21 | 22 | @Test 23 | public void callmeIntegration(Hoverfly hoverfly) { 24 | hoverfly.simulate( 25 | dsl(service("http://callme-service.pminkows-serverless.svc.cluster.local") 26 | .get("/callme/ping") 27 | .willReturn(success().body("I'm callme-service v1."))) 28 | ); 29 | String response = restTemplate.getForObject("/caller/ping", String.class); 30 | assertEquals("caller-service(null): test in default", response); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /callme-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | pl.piomin 9 | kubernetes-workshop 10 | 1.0 11 | 12 | callme-service 13 | 1.0 14 | 15 | 16 | ${project.artifactId} 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-actuator 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jpa 31 | 32 | 33 | com.h2database 34 | h2 35 | runtime 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-maven-plugin 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /callme-service/src/main/java/pl/piomin/samples/kubernetes/CallmeApplication.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.kubernetes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CallmeApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(CallmeApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /callme-service/src/main/java/pl/piomin/samples/kubernetes/controller/CallmeController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.kubernetes.controller; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | @RequestMapping("/callme") 8 | public class CallmeController { 9 | 10 | // TODO - Add fragment of code from instruction 11 | 12 | } 13 | -------------------------------------------------------------------------------- /callme-service/src/main/java/pl/piomin/samples/kubernetes/utils/AppVersion.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.kubernetes.utils; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class AppVersion { 7 | 8 | // TODO - Add source code fragment from instruction 9 | 10 | } 11 | -------------------------------------------------------------------------------- /callme-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.application.name: callme-service -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 3.5.0 11 | 12 | 13 | 14 | pl.piomin 15 | kubernetes-workshop 16 | pom 17 | 1.0 18 | 19 | callme-service 20 | caller-service 21 | 22 | 23 | 24 | piomin_kubernetes-workshop 25 | piomin 26 | https://sonarcloud.io 27 | 21 28 | 29 | 30 | 31 | 32 | 33 | org.jacoco 34 | jacoco-maven-plugin 35 | 0.8.13 36 | 37 | 38 | 39 | prepare-agent 40 | 41 | 42 | 43 | report 44 | test 45 | 46 | report 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Workshop 2 | Checkout branch `master`. It contains the starting version of source code. For the final version switch to branch `final`. 3 | With the source code version in `master` you can follow the instructions. 4 | 5 | ## Table of Contents 6 | 1. [Prerequisites](#1-prerequisites)\ 7 | 1.a) [JDK](#1a-jdk)\ 8 | 1.b) [Git](#1b-git)\ 9 | 1.c) [Maven](#1c-maven)\ 10 | 1.d) [Skaffold](#1d-skaffold)\ 11 | 1.e) [Istio](#1e-istio)\ 12 | 1.f) [Docker registry](#1f-docker-registry)\ 13 | 1.g) [Existing cluster on GKE](#1g-existing-cluster-on-gke)\ 14 | 1.h) [Disable auto-save (optional)](#1h-optionally-disable-auto-save-on-your-ide) 15 | 2. [Skaffold/Jib](#2-skaffoldjib)\ 16 | 2.a) [Initialize Skaffold for projects](#2a-initialize-skaffold-for-projects)\ 17 | 2.b) [Deploy applications in dev mode](#2b-deploy-applications-in-dev-mode)\ 18 | 2.c) [Deploy applications in debug mode](#2c-deploy-applications-in-debug-mode)\ 19 | 2.d) [Deploy applications in multi-module mode](#2d-initialize-skaffold-in-multi-module-mode) 20 | 3. [Development](#3-development)\ 21 | 3.a) [Inject metadata into container](#3a-inject-metadata-into-container)\ 22 | 3.b) [Add code](#3b-add-code)\ 23 | 3.c) [Inject labels into container](#3c-inject-labels-with-downwardapi)\ 24 | 3.d) [Read and print labels inside application](#3d-read-and-print-labels-inside-application) 25 | 4. [Install Istio](#4-install-istio) 26 | 5. [Configure traffic with Istio](#5-configure-traffic-with-istio)\ 27 | 5.a) [Split across versions](#5a-split-across-versions)\ 28 | 5.b) [Create Istio gateway](#5b-create-istio-gateway)\ 29 | 5.c) [Inter-service communication](#5c-inter-service-communication)\ 30 | 5.d) [Faults and timeouts](#5d-faults-and-timeouts) 31 | 32 | ### 1. Prerequisites 33 | 34 | #### 1.a) JDK 35 | Check Java (JDK) version by calling `java --version`. Version used in this workshop is 11.\ 36 | You can download it here: https://www.oracle.com/java/technologies/javase-jdk11-downloads.html. 37 | 38 | #### 1.b) Git 39 | Check Git Client version by calling `git version`.\ 40 | You can download it here: https://git-scm.com/downloads. 41 | 42 | #### 1.c) Maven 43 | Check Maven version by calling `mvn --version`.\ 44 | You can download it here: https://maven.apache.org/download.cgi. 45 | 46 | Example response: 47 | ``` 48 | Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-24T20:41:47+02:00)\ 49 | Maven home: C:\Users\minkowp\apache-maven-3.6.0\bin\..\ 50 | Java version: 11.0.1, vendor: Oracle Corporation, runtime: C:\Program Files\jdk-11.0.1\ 51 | ``` 52 | 53 | #### 1.d) Skaffold 54 | Check Skaffold version by calling `skaffold version`.\ 55 | You can download it here: https://skaffold.dev/docs/install/. 56 | 57 | #### 1.e) Istio 58 | Check Istio CLI version by calling `istioctl version`.\ 59 | You can download it here: https://github.com/istio/istio/releases/tag/1.7.2. 60 | 61 | #### 1.f) Docker Registry 62 | You need to have account on the remote Docker Registry like docker.io.\ 63 | Your docker.io username is then referred as `YOUR_DOCKER_USERNAME`. 64 | 65 | #### 1.g) Existing cluster on GKE 66 | Use `gcloud` command to initialize a new Kubernetes cluster using GKE services: 67 | 68 | ```shell script 69 | gcloud container clusters create cnw3 \ 70 | --zone europe-west3 \ 71 | --node-locations europe-west3-a,europe-west3-b,europe-west3-c \ 72 | --cluster-version=1.17 \ 73 | --disk-size=50GB \ 74 | --enable-autoscaling \ 75 | --num-nodes=1 \ 76 | --min-nodes=1 \ 77 | --max-nodes=3 78 | ``` 79 | 80 | After it has successfully started configure kubectl context: 81 | 82 | ```shell script 83 | gcloud container clusters get-credentials cnw3 --region=europe-west3 84 | ``` 85 | 86 | #### 1.h) Optionally disable auto-save on your IDE 87 | With Intellij go to File > Settings > Appearance & Behavior > System Settings.\ 88 | Uncheck the following: 89 | - Save files on frame deactivation 90 | - Save files automatically if application is idle for x sec. 91 | 92 | ### 2. Skaffold/Jib 93 | 94 | #### 2.a) Initialize Skaffold for projects 95 | My docker.io login is `piomin`, so I will just replace all occurrences of `` into `piomin`. It is used then in case of pushing image to remote registry.\ 96 | Go to callme-service `cd callme-service`.\ 97 | Execute command `skaffold init --XXenableJibInit`. Then you should see the message `One or more valid Kubernetes manifests are required to run skaffold`.\ 98 | Then you should create a directory `k8s` and place there a YAML manifest with `Deployment`.\ 99 | For example: 100 | ```yaml 101 | apiVersion: apps/v1 102 | kind: Deployment 103 | metadata: 104 | name: callme-deployment 105 | spec: 106 | selector: 107 | matchLabels: 108 | app: callme 109 | template: 110 | metadata: 111 | labels: 112 | app: callme 113 | spec: 114 | containers: 115 | - name: callme 116 | image: /callme-service 117 | ports: 118 | - containerPort: 8080 119 | ``` 120 | Then, add the following fragment inside `build.plugins` tag in `callme-service/pom.xml`. 121 | ```xml 122 | 123 | com.google.cloud.tools 124 | jib-maven-plugin 125 | 2.4.0 126 | 127 | ``` 128 | 129 | Now, execute command `skaffold init --XXenableJibInit` once again and accept communicate:\ 130 | "Do you want to write this configuration to skaffold.yaml? [y/n]". -> y\ 131 | The skaffold.yaml has been generated. 132 | ```yaml 133 | apiVersion: skaffold/v2beta5 134 | kind: Config 135 | metadata: 136 | name: callme-service 137 | build: 138 | artifacts: 139 | - image: /callme-service 140 | jib: 141 | project: pl.piomin.samples.kubernetes:callme-service 142 | deploy: 143 | kubectl: 144 | manifests: 145 | - k8s/deployment.yaml 146 | ``` 147 | You can remove all lines after line 8. 148 | ```yaml 149 | apiVersion: skaffold/v2beta5 150 | kind: Config 151 | metadata: 152 | name: callme-service 153 | build: 154 | artifacts: 155 | - image: /callme-service 156 | jib: {} 157 | ``` 158 | 159 | Go to caller-service directory `cd caller-service`. Then check if you have to add there anything else the same as for callme-service. 160 | You need to change into your login in `deployment.yaml`. 161 | 162 | #### 2.b) Deploy applications in dev mode 163 | First, let's create a dedicated namespace for our workshop: `kubectl create ns workshop`. (optional)\ 164 | Then let's set it as a default namespace for kubectl: `kubectl config set-context --current --namespace=workshop` (optional)\ 165 | Go to callme-service directory and run Skaffold: `skaffold dev -n workshop --port-forward`.\ 166 | Go to caller-service directory and run Skaffold: `skaffold dev -n workshop --port-forward`. 167 | 168 | #### 2.c) Deploy applications in debug mode 169 | Go to callme-service directory and run Skaffold: `skaffold debug -n workshop --port-forward`.\ 170 | Then you can find the following log (the number of forwarded port is important here): 171 | ``` 172 | Port forwarding pod/callme-deployment-6f595bb5f4-sgpvr in namespace workshop, remote port 5005 -> address 127.0.0.1 port 5005 173 | ... 174 | Picked up JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n,quiet=y 175 | ``` 176 | Go to caller-service directory and run Skaffold: `skaffold debug -n workshop --port-forward`.\ 177 | (Optional) Now we create a debugger. With IntelliJ go to Run -> Edit configurations... -> Templates -> Remote -> New...\ 178 | Then set the port number and module name (demo). 179 | 180 | #### 2.d) Initialize Skaffold in multi-module mode 181 | Go to root directory of project `cd ..`. \ 182 | Now, execute command `skaffold init --XXenableJibInit` in the root of Maven project:\ 183 | First choose "None" [ENTER], then \ 184 | "Do you want to write this configuration to skaffold.yaml? [y/n]". -> y\ 185 | The skaffold.yaml has been generated. 186 | ```yaml 187 | apiVersion: skaffold/v2beta5 188 | kind: Config 189 | metadata: 190 | name: kubernetes-workshop 191 | deploy: 192 | kubectl: 193 | manifests: 194 | - caller-service/k8s/deployment.yaml 195 | - callme-service/k8s/deployment.yaml 196 | ``` 197 | Let's modify the `skaffold.yaml` file into that: 198 | ```yaml 199 | apiVersion: skaffold/v2beta5 200 | kind: Config 201 | metadata: 202 | name: kubernetes-workshop 203 | build: 204 | artifacts: 205 | - image: /callme-service 206 | jib: 207 | project: callme-service 208 | - image: /caller-service 209 | jib: 210 | project: caller-service 211 | deploy: 212 | kubectl: 213 | manifests: 214 | - callme-service/k8s/*.yaml 215 | - caller-service/k8s/*.yaml 216 | - k8s/*.yaml 217 | ``` 218 | Then run Skaffold in dev mode: `skaffold dev -n workshop --port-forward`. 219 | 220 | ### 3. Development 221 | 222 | #### 3.a) Inject metadata into container 223 | Add the following environment variables to `Deployment` in section `spec.template.spec.containers` 224 | ```yaml 225 | env: 226 | - name: POD_NAME 227 | valueFrom: 228 | fieldRef: 229 | fieldPath: metadata.name 230 | - name: POD_NAMESPACE 231 | valueFrom: 232 | fieldRef: 233 | fieldPath: metadata.namespace 234 | ``` 235 | 236 | #### 3.b) Add code 237 | Go to callme-service -> `src/main/java` -> `pl.piomin.samples.kubernetes.CallmeController`.\ 238 | The name of package is just a proposition.\ 239 | ```java 240 | public class CallmeController { 241 | 242 | @Value("${spring.application.name}") 243 | private String appName; 244 | @Value("${POD_NAME}") 245 | private String podName; 246 | @Value("${POD_NAMESPACE}") 247 | private String podNamespace; 248 | 249 | @GetMapping("/ping") 250 | public String ping() { 251 | return appName + ": " + podName + " in " + podNamespace; 252 | } 253 | 254 | } 255 | ``` 256 | Here's my current structure of the project: 257 | 258 | callme-service\ 259 | --k8s/\ 260 | ----deployment.yaml\ 261 | --src/main/\ 262 | ----java/pl/piomin/samples/kubernetes/\ 263 | ------controller/\ 264 | --------CallmeController.java\ 265 | ------utils/\ 266 | --------AppVersion.java\ 267 | ------CallmeApplication.java\ 268 | ----resources/\ 269 | ------application.yml 270 | 271 | #### 3.c) Inject labels with `downwardAPI` 272 | Add volume to section `spec.template.spec` 273 | ```yaml 274 | volumes: 275 | - name: podinfo 276 | downwardAPI: 277 | items: 278 | - path: "labels" 279 | fieldRef: 280 | fieldPath: metadata.labels 281 | ``` 282 | Then mount volume to the container: 283 | ```yaml 284 | volumeMounts: 285 | - mountPath: /etc/podinfo 286 | name: podinfo 287 | ``` 288 | 289 | #### 3.d) Read and print labels inside application 290 | The implementation of bean responsible for reading `version` label from file inside a mounted volume is ready and visible below. 291 | ```java 292 | @Component 293 | public class AppVersion { 294 | 295 | public String getVersionLabel() { 296 | try (Stream stream = Files.lines(Paths.get("/etc/podinfo/labels"))) { 297 | stream.forEach(System.out::println); 298 | Optional optVersion = stream.filter(it -> it.startsWith("version=")).findFirst(); 299 | return optVersion.map(s -> s.split("=")[1].replace("\"", "")) 300 | .orElse("null"); 301 | } catch (IOException e) { 302 | e.printStackTrace(); 303 | } 304 | return "null"; 305 | } 306 | } 307 | ``` 308 | 309 | Go to callme-service -> `src/main/java` -> `pl.piomin.samples.kubernetes.CallmeController`.\ 310 | Then inject `AppVersion` bean and invoke method `getVersionLabel` in `ping` method. 311 | ``` 312 | @Autowired 313 | private AppVersion appVersion; 314 | 315 | @GetMapping("/ping") 316 | public String ping() { 317 | return appName + "(" + appVersion.getVersionLabel() + "): " + podName + " in " + podNamespace; 318 | } 319 | ``` 320 | 321 | Reload is finished automatically. The last line in logs is similar to the following one: 322 | ``` 323 | [callme-deployment-75f9d5ff44-4jwst callme] 2020-08-17 12:33:12.089 INFO 1 --- [ main] p.p.s.kubernetes.CallmeApplication : Started Cal 324 | lmeApplication in 6.589 seconds (JVM running for 7.403) 325 | ``` 326 | 327 | Call HTTP endpoint `GET /callme/ping`: `curl http://localhost:8080/callme/ping`.\ 328 | The response: `curl: (52) Empty reply from server`. 329 | 330 | Add `Service` definition in `callme-service/k8s/deployment.yaml`: 331 | ```yaml 332 | --- 333 | apiVersion: v1 334 | kind: Service 335 | metadata: 336 | name: callme-service 337 | spec: 338 | type: ClusterIP 339 | selector: 340 | app: callme 341 | ports: 342 | - port: 8080 343 | ``` 344 | 345 | After reload you should see the following line in the logs: 346 | ``` 347 | Port forwarding service/callme-service in namespace test, remote port 8080 -> address 127.0.0.1 port 8080 348 | ``` 349 | 350 | Call the endpoint once again: 351 | ```shell script 352 | curl http://localhost:8080/callme/ping 353 | {"timestamp":"2020-08-17T12:41:24.492+00:00","status":500,"error":"Internal Server Error","message":"","path":"/callme/ping"} 354 | ``` 355 | 356 | Go to the logs. Try to fix error. In case you had problems with it or you just want to skip it the proper implementation of `AppVersion` is inside `caller-service`. \ 357 | Go to caller-service -> `src/main/java` -> `pl.piomin.samples.kubernetes.utils.AppVersion`, and compare it with method in `callme-service`. \ 358 | After fix call the endpoint once again: 359 | ```shell script 360 | curl http://localhost:8080/callme/ping 361 | callme-service(null): callme-deployment-6d94874588-gs9vw in test 362 | ``` 363 | 364 | #### 3.e) Deploy two versions of application 365 | Before any change stop command `skaffold dev` with CTRL+C to clean resources. \ 366 | Go to `callme-service\k8s\deployment.yaml`. \ 367 | Add to `spec.template.metadata.labels` and to `spec.selector.matchLabels` the new label `version: v1`. \ 368 | Change the name of `Deployment` from `callme-deployment` to `callme-deployment`. \ 369 | Then create a second deployment `callme-deployment-v2`. 370 | ```yaml 371 | apiVersion: apps/v1 372 | kind: Deployment 373 | metadata: 374 | name: callme-deployment-v2 375 | spec: 376 | selector: 377 | matchLabels: 378 | app: callme 379 | version: v2 380 | template: 381 | metadata: 382 | labels: 383 | app: callme 384 | version: v2 385 | spec: 386 | containers: 387 | - name: callme 388 | image: /callme-service 389 | 390 | ports: 391 | - containerPort: 8080 392 | env: 393 | - name: POD_NAME 394 | valueFrom: 395 | fieldRef: 396 | fieldPath: metadata.name 397 | - name: POD_NAMESPACE 398 | valueFrom: 399 | fieldRef: 400 | fieldPath: metadata.namespace 401 | volumeMounts: 402 | - mountPath: /etc/podinfo 403 | name: podinfo 404 | volumes: 405 | - name: podinfo 406 | downwardAPI: 407 | items: 408 | - path: "labels" 409 | fieldRef: 410 | fieldPath: metadata.labels 411 | ``` 412 | 413 | After reload verify list of `Deployment` in your namespace. 414 | ```shell script 415 | kubectl get deploy -n test 416 | NAME READY UP-TO-DATE AVAILABLE AGE 417 | callme-deployment-v1 1/1 1 1 88s 418 | callme-deployment-v2 1/1 1 1 88s 419 | ``` 420 | 421 | Call the endpoint once again: `curl http://localhost:8080/callme/ping`. 422 | 423 | #### 3.f) Deploy `caller-service` 424 | Go to `caller-service` directory.\ 425 | Run `skaffold dev --port-forward`. You will probably have port 8081. 426 | Let's verify the state: 427 | ```shell script 428 | $ kubectl get deploy -n test 429 | NAME READY UP-TO-DATE AVAILABLE AGE 430 | callme-deployment-v1 1/1 1 1 88s 431 | callme-deployment-v2 1/1 1 1 88s 432 | caller-deployment-v1 1/1 1 1 44s 433 | caller-deployment-v2 1/1 1 1 44s 434 | ``` 435 | 436 | Call the endpoint: `curl http://localhost:8080/caller/ping`. 437 | 438 | ### 4. Install Istio 439 | If running on the local cluster: `istioctl install`. \ 440 | Enable Istio for your namespace: `kubectl label namespace istio-injection=enabled`. \ 441 | After installation you can the following commands: 442 | ```shell script 443 | $ istioctl version 444 | client version: 1.6.5 445 | control plane version: 1.6.5 446 | data plane version: 1.6.5 (4 proxies) 447 | ``` 448 | And then: 449 | ```shell script 450 | $ kubectl get pod -n istio-system 451 | NAME READY STATUS RESTARTS AGE 452 | istio-ingressgateway-54c4985b54-fhg7g 1/1 Running 0 1d 453 | istiod-585c5d45f5-qlwnd 1/1 Running 0 1d 454 | prometheus-547b4d6f8c-lrcbj 2/2 Running 0 1d 455 | ``` 456 | 457 | ### 5. Configure traffic with Istio 458 | 459 | #### 5.a) Split across versions 460 | Go to callme-service/k8s directory. Create the file `istio.yaml`. You can pick any name. 461 | Create `DestinationRule` with 2 subsets: `v1` and `v2`. 462 | ```yaml 463 | apiVersion: networking.istio.io/v1beta1 464 | kind: DestinationRule 465 | metadata: 466 | name: callme-service-destination 467 | spec: 468 | host: callme-service.workshop.svc.cluster.local 469 | subsets: 470 | - name: v1 471 | labels: 472 | version: v1 473 | - name: v2 474 | labels: 475 | version: v2 476 | ``` 477 | Create `VirtualService` with 3 routes splitted by HTTP header: `v1`, `v2`, no header. 478 | ```yaml 479 | apiVersion: networking.istio.io/v1beta1 480 | kind: VirtualService 481 | metadata: 482 | name: callme-service-route 483 | spec: 484 | hosts: 485 | - callme-service.workshop.svc.cluster.local 486 | http: 487 | - match: 488 | - headers: 489 | X-Version: 490 | exact: v1 491 | route: 492 | - destination: 493 | host: callme-service.workshop.svc.cluster.local 494 | subset: v1 495 | - match: 496 | - headers: 497 | X-Version: 498 | exact: v2 499 | route: 500 | - destination: 501 | host: callme-service.workshop.svc.cluster.local 502 | subset: v2 503 | - route: 504 | - destination: 505 | host: callme-service.workshop.svc.cluster.local 506 | subset: v1 507 | weight: 20 508 | - destination: 509 | host: callme-service.workshop.svc.cluster.local 510 | subset: v2 511 | weight: 80 512 | ``` 513 | Run the following command to verify list of `VirtualService`. 514 | ```shell script 515 | $ kubectl get vs 516 | NAME GATEWAYS HOSTS AGE 517 | callme-service-route [callme-service.test.svc.cluster.local] 24s 518 | ``` 519 | 520 | #### 5.b) Create Istio gateway 521 | Create k8s in the root of project. Then create the file `istio.yaml`.\ 522 | Create Istio `Gateway` available on "any" host and port 80.\ 523 | Omit creating `Gateway` if you are **not using your own Kubernetes instance**. 524 | ```yaml 525 | apiVersion: networking.istio.io/v1beta1 526 | kind: Gateway 527 | metadata: 528 | name: microservices-gateway 529 | spec: 530 | selector: 531 | istio: ingressgateway 532 | servers: 533 | - port: 534 | number: 80 535 | name: http 536 | protocol: HTTP 537 | hosts: 538 | - "*" 539 | ``` 540 | Then apply it using the following command. 541 | ```shell script 542 | $ kubectl apply -f k8s/istio.yaml 543 | ``` 544 | 545 | Create another file `istio-with-gateway.yaml` in directory callme-service/k8s.\ 546 | Add Istio `VirtualService` that references to `microservices-gateway` `Gateway`. 547 | ```yaml 548 | apiVersion: networking.istio.io/v1beta1 549 | kind: VirtualService 550 | metadata: 551 | name: callme-service-gateway-route 552 | spec: 553 | hosts: 554 | - "*" 555 | gateways: 556 | - microservices-gateway 557 | http: 558 | - match: 559 | - headers: 560 | X-Version: 561 | exact: v1 562 | uri: 563 | prefix: "/callme" 564 | rewrite: 565 | uri: " " 566 | route: 567 | - destination: 568 | host: callme-service.workshop.svc.cluster.local 569 | subset: v1 570 | - match: 571 | - uri: 572 | prefix: "/callme" 573 | headers: 574 | X-Version: 575 | exact: v2 576 | rewrite: 577 | uri: " " 578 | route: 579 | - destination: 580 | host: callme-service.workshop.svc.cluster.local 581 | subset: v2 582 | - match: 583 | - uri: 584 | prefix: "/callme" 585 | rewrite: 586 | uri: " " 587 | route: 588 | - destination: 589 | host: callme-service.workshop.svc.cluster.local 590 | subset: v1 591 | weight: 20 592 | - destination: 593 | host: callme-service.workshop.svc.cluster.local 594 | subset: v2 595 | weight: 80 596 | ``` 597 | 598 | Let's verify list of `VirtualService`. 599 | ```shell script 600 | $ kubectl get vs 601 | NAME GATEWAYS HOSTS AGE 602 | callme-service-gateway-route [microservices-gateway] [*] 45s 603 | callme-service-route [callme-service.test.svc.cluster.local] 15m 604 | ``` 605 | 606 | Verify address of `istio-ingressgateway` in `istio-system` namespace. 607 | ```shell script 608 | kubectl get svc -n istio-system 609 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 610 | istio-ingressgateway LoadBalancer 10.110.87.104 localhost 15021:31846/TCP,80:31466/TCP,443:30947/TCP,15443:30736/TCP 41d 611 | istiod ClusterIP 10.106.253.175 15010/TCP,15012/TCP,443/TCP,15014/TCP,853/TCP 41d 612 | prometheus ClusterIP 10.108.45.212 9090/TCP 41d 613 | ``` 614 | 615 | Let's `GET /callme/ping` endpoint with header `X-Version=v1`, `X-Version=v2` or `X-Version=null`.\ 616 | Change `localhost` into your IP address in "EXTERNAL-IP" 617 | ```shell script 618 | $ curl http://localhost/callme/callme/ping -H "X-Version:v1" 619 | callme-service(v1): callme-deployment-v1-57d8c69586-m67f5 in test 620 | $ curl http://localhost/callme/callme/ping -H "X-Version:v2" 621 | callme-service(v2): callme-deployment-v2-774f46f699-tcpw8 in test 622 | $ curl http://localhost/callme/callme/ping 623 | callme-service(v2): callme-deployment-v2-774f46f699-tcpw8 in test 624 | $ curl http://localhost/callme/callme/ping 625 | callme-service(v1): callme-deployment-v1-57d8c69586-m67f5 in test 626 | ``` 627 | 628 | #### 5.c) Inter-service communication 629 | Go to caller-service/src/main/java/pl/piomin/samples/kubernetes/controller directory.\ 630 | Edit file `CallerController.java`. Then change existing endpoint implementation of `GET \caller\ping` endpoint.\ 631 | You need to add `@RequestHeader`, invoke method `callme(version)`, and change return statement by adding response from callme-service.\ 632 | The final implementation is visible below. 633 | ```java 634 | @RestController 635 | @RequestMapping("/caller") 636 | public class CallerController { 637 | 638 | // ... 639 | 640 | @GetMapping("/ping") 641 | public String ping(@RequestHeader(name = "X-Version", required = false) String version) { 642 | String callme = callme(version); 643 | return appName + "(" + appVersion.getVersionLabel() + "): " + podName + " in " + podNamespace 644 | + " is calling " + callme; 645 | } 646 | 647 | // ... 648 | 649 | } 650 | ``` 651 | 652 | Based on callme-service `DestinationRule` and `VirtualService` try to create the same set for caller-service. We need to create only a single `VirtualService` that refers to the gateway. 653 | 654 | Deploy caller-service. Go to caller-service and run Skaffold: `skaffold dev -n workshop --port-forward`.\ 655 | The starting version of Istio manifest `istio.yaml` is available in directory caller-service/k8s. Use it in the beginning. We will change it in the next steps. 656 | Then verify list of Istio virtual services. 657 | ```shell script 658 | $ kubectl get vs -n test 659 | NAME GATEWAYS HOSTS AGE 660 | caller-service-gateway-route [microservices-gateway] [*] 24s 661 | callme-service-gateway-route [microservices-gateway] [*] 64m 662 | callme-service-route [callme-service.test.svc.cluster.local] 79m 663 | ``` 664 | 665 | Then send some test requests with header `X-Version` set to `v1`, `v2` or not set. 666 | ```shell script 667 | $ curl http://localhost/caller/caller/ping -H "X-Version:v1" 668 | caller-service(v1): caller-deployment-v1-54f6486b5-4ltpw in test is calling callme-service(v1): callme-deployment-v1-64cb6c744d-9z7w2 in test 669 | $ curl http://localhost/caller/caller/ping -H "X-Version:v2" 670 | caller-service(v2): caller-deployment-v2-67969c6cc6-rvsk4 in test is calling callme-service(v2): callme-deployment-v2-864494769c-lm7sg in test 671 | $ curl http://localhost/caller/caller/ping 672 | caller-service(v2): caller-deployment-v2-879ffc844-nqsd6 in test is calling callme-service(v2): callme-deployment-v2-864494769c-lm7sg in test 673 | $ curl http://localhost/caller/caller/ping 674 | caller-service(v2): caller-deployment-v2-879ffc844-nqsd6 in test is calling callme-service(v1): callme-deployment-v1-64cb6c744d-9z7w2 in test 675 | ``` 676 | 677 | #### 5.d) Faults and timeouts 678 | Go to callme-service/k8s. Edit `istio.yml` file. 679 | Add the following code to section `spec.http[0].route` defined for the `v1` version. 680 | ```yaml 681 | fault: 682 | abort: 683 | percentage: 684 | value: 50 685 | httpStatus: 400 686 | ``` 687 | Similarly, add he following code to section `spec.http[1].route` defined for the `v2` version. 688 | ```yaml 689 | fault: 690 | delay: 691 | percentage: 692 | value: 50 693 | fixedDelay: 3s 694 | ``` 695 | Leave third route without any changes. Save files, the changes are applied automatically by skaffold. 696 | Let's call endpoint with `v1` version. Since 50% of requests are finished with HTTP 400 you may repeat the request several times. \ 697 | ```shell script 698 | $ curl http://localhost/caller/caller/ping -H "X-Version:v1" 699 | {"timestamp":"2020-09-02T13:10:43.136+00:00","status":500,"error":"Internal Server Error","message":"","path":"/caller/ping"} 700 | ``` 701 | You should see the following log in caller-service. 702 | ``` 703 | 2020-09-02 13:09:54.619 ERROR 1 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : 704 | Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springfra 705 | mework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request: [fault filter abort]] with root cause 706 | ``` 707 | 708 | Let's call endpoint with `v1` version. Since 50% of requests are delayed 3s you may repeat the request several times. 709 | ```shell script 710 | $ curl http://localhost/caller/caller/ping -H "X-Version:v2" -w "\nTime: %{time_total}\n" -v 711 | * Trying ::1... 712 | * TCP_NODELAY set 713 | * Connected to localhost (::1) port 80 (#0) 714 | > GET /caller/caller/ping HTTP/1.1 715 | > Host: localhost 716 | > User-Agent: curl/7.55.1 717 | > Accept: */* 718 | > X-Version:v2 719 | > 720 | < HTTP/1.1 200 OK 721 | < content-type: text/plain;charset=UTF-8 722 | < content-length: 142 723 | < date: Wed, 02 Sep 2020 13:24:35 GMT 724 | < x-envoy-upstream-service-time: 3022 725 | < server: istio-envoy 726 | < 727 | caller-service(v2): caller-deployment-v2-7d57f6799c-4h46t in test is calling callme-service(v2): callme-deployment-v2-84f947d86d-lgjqq in test* Connecti 728 | on #0 to host localhost left intact 729 | 730 | Time: 3,093000 731 | ``` 732 | There is fault injection in the following request, that does not contain version. 733 | ```shell script 734 | $ curl http://localhost/caller/caller/ping 735 | ``` 736 | 737 | Go to caller-service/k8s. Edit file `istio.yaml`. 738 | Add the following line to the section `spec.http[0]`. Then save changes. 739 | ```yaml 740 | timeout: 1s 741 | ``` 742 | Let's call caller-service with `v2`. The HTTP 504 Gateway Timeout is after 1s. 743 | ```shell script 744 | $ curl http://localhost/caller/caller/ping -H "X-Version:v2" -w "\nTime: %{time_total}\n" -v 745 | * Trying ::1... 746 | * TCP_NODELAY set 747 | * Connected to localhost (::1) port 80 (#0) 748 | > GET /caller/caller/ping HTTP/1.1 749 | > Host: localhost 750 | > User-Agent: curl/7.55.1 751 | > Accept: */* 752 | > X-Version:v2 753 | > 754 | < HTTP/1.1 504 Gateway Timeout 755 | < content-length: 24 756 | < content-type: text/plain 757 | < date: Wed, 02 Sep 2020 13:34:14 GMT 758 | < server: istio-envoy 759 | < 760 | upstream request timeout* Connection #0 to host localhost left intact 761 | 762 | Time: 1,094000 763 | ``` 764 | Before the next step let's enable Envoy's access logs. 765 | ```shell script 766 | $ istioctl install --set meshConfig.accessLogFile="/dev/stdout" 767 | ``` 768 | 769 | Go to caller-service/k8s. Edit file `istio.yaml`. 770 | Add the following line to the section `spec.http[0]`. Then save changes. 771 | ```yaml 772 | retries: 773 | attempts: 3 774 | retryOn: 5xx 775 | ``` 776 | 777 | Let's call endpoint caller-service with `v1`. 778 | ```shell script 779 | $ curl http://localhost/caller/caller/ping -H "X-Version:v1" 780 | caller-service(v1): caller-deployment-v1-78988d4cfb-tqhqh in test is calling callme-service(v1): callme-deployment-v1-6b5f5fdfb9-qtq52 in test 781 | ``` 782 | 783 | Go to the logs available on the terminal with `skaffold dev` for caller-service.\ 784 | Although we receive a proper value with HTTP 200, the request has been retried by Istio. 785 | ``` 786 | [caller-deployment-v1-78988d4cfb-tqhqh istio-proxy] [2020-09-02T13:56:58.788Z] "GET /callme/ping HTTP/1.1" 400 FI "-" "-" 0 18 0 - "-" "Java/11 787 | .0.6" "1bf27689-98bc-4058-819e-5750c21d1f8c" "callme-service:8080" "-" - - 10.106.101.0:8080 10.1.2.21:54776 - - 788 | [caller-deployment-v1-78988d4cfb-tqhqh istio-proxy] [2020-09-02T13:55:59.609Z] "- - -" 0 - "-" "-" 4425 783 59196 - "-" "-" "-" "-" "127.0.0.1: 789 | 8080" inbound|8080||caller-service.test.svc.cluster.local 127.0.0.1:52466 10.1.2.21:8080 10.1.2.14:57982 outbound_.8080_.v1_.caller-service.test.svc.clu 790 | ster.local - 791 | [caller-deployment-v1-78988d4cfb-tqhqh istio-proxy] [2020-09-02T13:56:58.831Z] "GET /callme/ping HTTP/1.1" 200 - "-" "-" 0 65 5 5 "-" "Java/11. 792 | 0.6" "b133ee29-a1d9-415b-bd3a-4364d50119f9" "callme-service:8080" "10.1.2.22:8080" outbound|8080|v1|callme-service.test.svc.cluster.local 10.1.2.21:5069 793 | 2 10.106.101.0:8080 10.1.2.21:54776 - - 794 | [caller-deployment-v1-78988d4cfb-tqhqh istio-proxy] [2020-09-02T13:55:58.327Z] "- - -" 0 - "-" "-" 1475 257 61516 - "-" "-" "-" "-" "127.0.0.1: 795 | 8080" inbound|8080||caller-service.test.svc.cluster.local 127.0.0.1:52438 10.1.2.21:8080 10.1.2.14:57954 outbound_.8080_.v1_.caller-service.test.svc.clu 796 | ster.local - 797 | ``` 798 | 799 | Go to caller-service/k8s. Edit file `istio.yaml`. 800 | Add the following line to the section `spec.http[1]`. Then save changes. 801 | ```yaml 802 | retries: 803 | attempts: 3 804 | retryOn: 5xx 805 | perTryTimeout: 0.33s 806 | ``` 807 | Then call caller-service `v2`. 808 | ```shell script 809 | $ curl http://localhost/caller/caller/ping -H "X-Version:v2" -w "\nTime: %{time_total}\n" -v 810 | ``` 811 | 812 | Here's the final `VirtualService` used in the part 5 of our exercise. 813 | ```yaml 814 | apiVersion: networking.istio.io/v1beta1 815 | kind: VirtualService 816 | metadata: 817 | name: caller-service-gateway-route 818 | spec: 819 | hosts: 820 | - "*" 821 | gateways: 822 | - microservices-gateway 823 | http: 824 | - match: 825 | - headers: 826 | X-Version: 827 | exact: v1 828 | uri: 829 | prefix: "/caller" 830 | rewrite: 831 | uri: " " 832 | route: 833 | - destination: 834 | host: caller-service.workshop.svc.cluster.local 835 | subset: v1 836 | retries: 837 | attempts: 3 838 | retryOn: 5xx 839 | - match: 840 | - uri: 841 | prefix: "/caller" 842 | headers: 843 | X-Version: 844 | exact: v2 845 | rewrite: 846 | uri: " " 847 | route: 848 | - destination: 849 | host: caller-service.workshop.svc.cluster.local 850 | subset: v2 851 | timeout: 1s 852 | retries: 853 | attempts: 3 854 | retryOn: 5xx 855 | perTryTimeout: 0.33s 856 | - match: 857 | - uri: 858 | prefix: "/caller" 859 | rewrite: 860 | uri: " " 861 | route: 862 | - destination: 863 | host: caller-service.workshop.svc.cluster.local 864 | subset: v1 865 | weight: 20 866 | - destination: 867 | host: caller-service.workshop.svc.cluster.local 868 | subset: v2 869 | weight: 80 870 | ``` -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base",":dependencyDashboard" 5 | ], 6 | "packageRules": [ 7 | { 8 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 9 | "automerge": true 10 | } 11 | ], 12 | "prCreation": "not-pending" 13 | } --------------------------------------------------------------------------------