├── Chapter01 ├── DockerFile └── docker-example.sh ├── Chapter02 ├── azure-vote-redis-all-in-one.yaml ├── azure-vote.yaml └── docker-compose.yaml ├── Chapter03 ├── example-redis-config.yaml ├── frontend-deployment.yaml ├── frontend-service.yaml ├── mariadb_statefulset.yaml ├── nillsf.yaml ├── redis-config ├── redis-config.yaml ├── redis-master-deployment.yaml ├── redis-master-deployment.yml ├── redis-master-deployment_Modified.yml ├── redis-master-service.yaml ├── redis-slave-deployment.yaml └── redis-slave-service.yaml ├── Chapter04 ├── frontend-image-patch.yaml ├── frontend-service.yaml ├── guestbook-all-in-one.yaml └── hpa.yaml ├── Chapter05 └── guestbook-all-in-one.yaml ├── Chapter06 ├── certificate-issuer-prod.yaml ├── certificate-issuer.yaml ├── frontend-oauth2-ingress.yaml ├── guestbook-all-in-one.yaml ├── ingress-with-tls-prod.yaml ├── ingress-with-tls.yaml ├── oauth2_deployment.yaml ├── oauth2_ingress.yaml ├── oauth2_service.yaml └── simple-frontend-ingress.yaml ├── Chapter07 ├── guestbook-all-in-one.yaml ├── healthy.html ├── index1.html ├── index2.html ├── testWeb.sh ├── webdeploy1.yaml ├── webdeploy2.yaml └── webservice.yaml ├── Chapter08 └── guestbook.php ├── Chapter09 └── social-network │ ├── .dockerignore │ ├── .travis.yml │ ├── Dockerfile │ ├── LICENSE │ ├── README.md │ ├── deployment │ ├── docker │ │ ├── docker-compose-build.yml │ │ └── docker_push │ ├── helm │ │ ├── .gitignore │ │ ├── edge-service │ │ │ ├── .helmignore │ │ │ ├── Chart.yaml │ │ │ ├── templates │ │ │ │ ├── NOTES.txt │ │ │ │ ├── _helpers.tpl │ │ │ │ ├── cm-app.yaml │ │ │ │ ├── deployment.yaml │ │ │ │ ├── rbac.yaml │ │ │ │ ├── service.yaml │ │ │ │ └── serviceaccount.yaml │ │ │ └── values.yaml │ │ ├── friend-service │ │ │ ├── .helmignore │ │ │ ├── Chart.yaml │ │ │ ├── requirements.lock │ │ │ ├── requirements.yaml │ │ │ ├── templates │ │ │ │ ├── NOTES.txt │ │ │ │ ├── _helpers.tpl │ │ │ │ ├── deployment.yaml │ │ │ │ ├── rbac.yaml │ │ │ │ ├── service.yaml │ │ │ │ └── serviceaccount.yaml │ │ │ └── values.yaml │ │ ├── recommendation-service │ │ │ ├── .helmignore │ │ │ ├── Chart.yaml │ │ │ ├── requirements.lock │ │ │ ├── requirements.yaml │ │ │ ├── templates │ │ │ │ ├── NOTES.txt │ │ │ │ ├── _helpers.tpl │ │ │ │ ├── deployment.yaml │ │ │ │ ├── rbac.yaml │ │ │ │ ├── service.yaml │ │ │ │ └── serviceaccount.yaml │ │ │ └── values.yaml │ │ ├── social-network │ │ │ ├── .helmignore │ │ │ ├── Chart.yaml │ │ │ ├── README.md │ │ │ ├── charts │ │ │ │ ├── edge-service │ │ │ │ │ ├── .helmignore │ │ │ │ │ ├── Chart.yaml │ │ │ │ │ ├── templates │ │ │ │ │ │ ├── NOTES.txt │ │ │ │ │ │ ├── _helpers.tpl │ │ │ │ │ │ ├── cm-app.yaml │ │ │ │ │ │ ├── deployment.yaml │ │ │ │ │ │ ├── rbac.yaml │ │ │ │ │ │ ├── service.yaml │ │ │ │ │ │ └── serviceaccount.yaml │ │ │ │ │ └── values.yaml │ │ │ │ ├── friend-service │ │ │ │ │ ├── .helmignore │ │ │ │ │ ├── Chart.yaml │ │ │ │ │ ├── requirements.lock │ │ │ │ │ ├── requirements.yaml │ │ │ │ │ ├── templates │ │ │ │ │ │ ├── NOTES.txt │ │ │ │ │ │ ├── _helpers.tpl │ │ │ │ │ │ ├── deployment.yaml │ │ │ │ │ │ ├── rbac.yaml │ │ │ │ │ │ ├── service.yaml │ │ │ │ │ │ └── serviceaccount.yaml │ │ │ │ │ └── values.yaml │ │ │ │ ├── recommendation-service │ │ │ │ │ ├── .helmignore │ │ │ │ │ ├── Chart.yaml │ │ │ │ │ ├── requirements.lock │ │ │ │ │ ├── requirements.yaml │ │ │ │ │ ├── templates │ │ │ │ │ │ ├── NOTES.txt │ │ │ │ │ │ ├── _helpers.tpl │ │ │ │ │ │ ├── deployment.yaml │ │ │ │ │ │ ├── rbac.yaml │ │ │ │ │ │ ├── service.yaml │ │ │ │ │ │ └── serviceaccount.yaml │ │ │ │ │ └── values.yaml │ │ │ │ └── user-service │ │ │ │ │ ├── .helmignore │ │ │ │ │ ├── Chart.yaml │ │ │ │ │ ├── requirements.lock │ │ │ │ │ ├── requirements.yaml │ │ │ │ │ ├── templates │ │ │ │ │ ├── NOTES.txt │ │ │ │ │ ├── _helpers.tpl │ │ │ │ │ ├── deployment.yaml │ │ │ │ │ ├── rbac.yaml │ │ │ │ │ ├── service.yaml │ │ │ │ │ └── serviceaccount.yaml │ │ │ │ │ └── values.yaml │ │ │ ├── dashboards │ │ │ │ ├── domain.json │ │ │ │ └── spring.json │ │ │ ├── requirements.lock │ │ │ ├── requirements.yaml │ │ │ ├── templates │ │ │ │ ├── NOTES.txt │ │ │ │ ├── _helpers.tpl │ │ │ │ └── grafana-dashboard-spring.yaml │ │ │ └── values.yaml │ │ └── user-service │ │ │ ├── .helmignore │ │ │ ├── Chart.yaml │ │ │ ├── requirements.lock │ │ │ ├── requirements.yaml │ │ │ ├── templates │ │ │ ├── NOTES.txt │ │ │ ├── _helpers.tpl │ │ │ ├── deployment.yaml │ │ │ ├── rbac.yaml │ │ │ ├── service.yaml │ │ │ └── serviceaccount.yaml │ │ │ └── values.yaml │ └── sbin │ │ ├── generate-parallel.sh │ │ ├── generate-serial.sh │ │ ├── names-100.txt │ │ └── names-15.txt │ ├── discovery-service │ ├── Dockerfile │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── io │ │ │ │ └── example │ │ │ │ └── DiscoveryServiceApplication.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── bootstrap.yml │ │ └── test │ │ └── java │ │ └── io │ │ └── example │ │ └── DiscoveryServiceApplicationTests.java │ ├── docker-compose.yml │ ├── edge-service │ ├── Dockerfile │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── io │ │ │ │ └── example │ │ │ │ └── EdgeServiceApplication.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── bootstrap.yml │ │ └── test │ │ └── java │ │ └── io │ │ └── example │ │ └── EdgeServiceApplicationTests.java │ ├── friend-service │ ├── Dockerfile │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── io │ │ │ │ └── example │ │ │ │ ├── FriendServiceApplication.java │ │ │ │ ├── config │ │ │ │ └── DataSourceConfiguration.java │ │ │ │ └── domain │ │ │ │ ├── friend │ │ │ │ ├── DomainEvent.java │ │ │ │ ├── EventType.java │ │ │ │ ├── Friend.java │ │ │ │ ├── FriendController.java │ │ │ │ ├── FriendEvent.java │ │ │ │ ├── FriendMessage.java │ │ │ │ ├── FriendRepository.java │ │ │ │ └── FriendService.java │ │ │ │ └── user │ │ │ │ ├── User.java │ │ │ │ └── UserClient.java │ │ └── resources │ │ │ ├── application.yml │ │ │ ├── bootstrap.yml │ │ │ └── db │ │ │ └── changelog │ │ │ └── db.changelog-master.yaml │ │ └── test │ │ └── java │ │ └── io │ │ └── example │ │ ├── AbstractIntegrationTest.java │ │ ├── AbstractUnitTest.java │ │ └── domain │ │ ├── FriendControllerTest.java │ │ └── FriendServiceTests.java │ ├── neo4j-service.yaml │ ├── pom.xml │ ├── recommendation-service │ ├── Dockerfile │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── io │ │ │ │ └── example │ │ │ │ ├── RecommendationService.java │ │ │ │ └── domain │ │ │ │ ├── friend │ │ │ │ ├── FriendEvent.java │ │ │ │ ├── FriendEventType.java │ │ │ │ ├── FriendMessage.java │ │ │ │ ├── FriendProcessor.java │ │ │ │ ├── FriendRepository.java │ │ │ │ ├── FriendSink.java │ │ │ │ └── entity │ │ │ │ │ ├── Friend.java │ │ │ │ │ └── RankedUser.java │ │ │ │ └── user │ │ │ │ ├── UserController.java │ │ │ │ ├── UserEvent.java │ │ │ │ ├── UserEventType.java │ │ │ │ ├── UserProcessor.java │ │ │ │ ├── UserRepository.java │ │ │ │ ├── UserSink.java │ │ │ │ └── entity │ │ │ │ └── User.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── bootstrap.yml │ │ └── test │ │ └── java │ │ └── io │ │ └── example │ │ ├── RecommendationServiceTests.java │ │ └── domain │ │ └── user │ │ └── UserControllerTest.java │ ├── temp.json │ └── user-service │ ├── Dockerfile │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── example │ │ │ ├── UserServiceApplication.java │ │ │ ├── config │ │ │ └── DataSourceConfiguration.java │ │ │ └── domain │ │ │ ├── DomainEvent.java │ │ │ ├── EventType.java │ │ │ ├── User.java │ │ │ ├── UserController.java │ │ │ ├── UserEvent.java │ │ │ ├── UserRepository.java │ │ │ └── UserService.java │ └── resources │ │ ├── application.yml │ │ ├── bootstrap.yml │ │ └── db │ │ └── changelog │ │ └── db.changelog-master.yaml │ └── test │ └── java │ └── io │ └── example │ ├── AbstractIntegrationTest.java │ ├── AbstractUnitTest.java │ └── domain │ ├── UserControllerTest.java │ └── UserServiceTests.java ├── Chapter10 ├── azure-vote.yaml ├── bookinfo.yaml ├── cluster-aad.sh ├── clusterRole.yaml ├── clusterRoleBinding.yaml ├── create_generic_secrets.sh ├── destinationRule.yaml ├── gen_secrets.sh ├── httpbin.yaml ├── kv-flexvol-installer.yaml ├── mtls_policy.yaml ├── myfirstsecret.yaml ├── pod-with-env-secrets.yaml ├── pod-with-vol-secret.yaml ├── pod_secret_flex.yaml ├── role.yaml ├── roleBinding.yaml ├── secrettoken.txt ├── secreturl.txt ├── sleep.yaml └── test_mtls.sh ├── Chapter11 ├── http │ ├── .dockerignore │ ├── .gitignore │ ├── .vscode │ │ └── extensions.json │ ├── Dockerfile │ ├── azure-functions-core-tools │ │ └── .telemetry │ │ │ └── 20200219024525_e379261df05b40d1906718fb3bb1238a.trn │ ├── host.json │ ├── python-http │ │ ├── __init__.py │ │ └── function.json │ └── requirements.txt ├── js-queue │ ├── .dockerignore │ ├── .gitignore │ ├── .vscode │ │ └── extensions.json │ ├── Dockerfile │ ├── azure-functions-core-tools │ │ └── .telemetry │ │ │ └── 20200220040920_1d8e395b746346be87941c639630ab9c.trn │ ├── host.json │ ├── js-queue │ │ ├── function.json │ │ ├── index.js │ │ └── readme.md │ └── package.json └── sendMessages.py ├── LICENSE └── README.md /Chapter01/DockerFile: -------------------------------------------------------------------------------- 1 | FROM docker/whalesay:latest 2 | RUN apt-get -y -qq update && apt-get install -qq -y fortunes 3 | CMD /usr/games/fortune -a | cowsay -------------------------------------------------------------------------------- /Chapter01/docker-example.sh: -------------------------------------------------------------------------------- 1 | #First we will pull an image 2 | docker pull docker/whalesay 3 | 4 | #We can then look at which images we have locally 5 | docker images 6 | 7 | #Then we will run our container 8 | docker run docker/whalesay cowsay boo -------------------------------------------------------------------------------- /Chapter02/azure-vote-redis-all-in-one.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: azure-vote-back 5 | spec: 6 | replicas: 1 7 | template: 8 | metadata: 9 | labels: 10 | app: azure-vote-back 11 | spec: 12 | nodeSelector: 13 | "beta.kubernetes.io/os": linux 14 | containers: 15 | - name: azure-vote-back 16 | image: redis 17 | ports: 18 | - containerPort: 6379 19 | name: redis 20 | --- 21 | apiVersion: v1 22 | kind: Service 23 | metadata: 24 | name: azure-vote-back 25 | spec: 26 | ports: 27 | - port: 6379 28 | selector: 29 | app: azure-vote-back 30 | --- 31 | apiVersion: apps/v1beta1 32 | kind: Deployment 33 | metadata: 34 | name: azure-vote-front 35 | spec: 36 | replicas: 1 37 | strategy: 38 | rollingUpdate: 39 | maxSurge: 1 40 | maxUnavailable: 1 41 | minReadySeconds: 5 42 | template: 43 | metadata: 44 | labels: 45 | app: azure-vote-front 46 | spec: 47 | nodeSelector: 48 | "beta.kubernetes.io/os": linux 49 | containers: 50 | - name: azure-vote-front 51 | image: microsoft/azure-vote-front:v1 52 | ports: 53 | - containerPort: 80 54 | resources: 55 | requests: 56 | cpu: 250m 57 | limits: 58 | cpu: 500m 59 | env: 60 | - name: REDIS 61 | value: "azure-vote-back" 62 | --- 63 | apiVersion: v1 64 | kind: Service 65 | metadata: 66 | name: azure-vote-front 67 | spec: 68 | type: LoadBalancer 69 | ports: 70 | - port: 80 71 | selector: 72 | app: azure-vote-front -------------------------------------------------------------------------------- /Chapter02/azure-vote.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: azure-vote-back 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: azure-vote-back 10 | template: 11 | metadata: 12 | labels: 13 | app: azure-vote-back 14 | spec: 15 | containers: 16 | - name: azure-vote-back 17 | image: redis 18 | resources: 19 | requests: 20 | cpu: 100m 21 | memory: 128Mi 22 | limits: 23 | cpu: 250m 24 | memory: 256Mi 25 | ports: 26 | - containerPort: 6379 27 | name: redis 28 | --- 29 | apiVersion: v1 30 | kind: Service 31 | metadata: 32 | name: azure-vote-back 33 | spec: 34 | ports: 35 | - port: 6379 36 | selector: 37 | app: azure-vote-back 38 | --- 39 | apiVersion: apps/v1 40 | kind: Deployment 41 | metadata: 42 | name: azure-vote-front 43 | spec: 44 | replicas: 1 45 | selector: 46 | matchLabels: 47 | app: azure-vote-front 48 | template: 49 | metadata: 50 | labels: 51 | app: azure-vote-front 52 | spec: 53 | containers: 54 | - name: azure-vote-front 55 | image: microsoft/azure-vote-front:v1 56 | resources: 57 | requests: 58 | cpu: 100m 59 | memory: 128Mi 60 | limits: 61 | cpu: 250m 62 | memory: 256Mi 63 | ports: 64 | - containerPort: 80 65 | env: 66 | - name: REDIS 67 | value: "azure-vote-back" 68 | --- 69 | apiVersion: v1 70 | kind: Service 71 | metadata: 72 | name: azure-vote-front 73 | spec: 74 | type: LoadBalancer 75 | ports: 76 | - port: 80 77 | selector: 78 | app: azure-vote-front 79 | -------------------------------------------------------------------------------- /Chapter02/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | azure-vote-back: 4 | image: redis 5 | container_name: azure-vote-back 6 | ports: 7 | - "6379:6379" 8 | 9 | azure-vote-front: 10 | build: ./azure-vote 11 | image: azure-vote-front 12 | container_name: azure-vote-front 13 | environment: 14 | REDIS: azure-vote-back 15 | ports: 16 | - "8080:80" 17 | -------------------------------------------------------------------------------- /Chapter03/example-redis-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | redis-config: |- 4 | maxmemory 2mb 5 | maxmemory-policy allkeys-lru 6 | kind: ConfigMap 7 | metadata: 8 | name: example-redis-config 9 | namespace: default -------------------------------------------------------------------------------- /Chapter03/frontend-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: frontend 5 | labels: 6 | app: guestbook 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: guestbook 11 | tier: frontend 12 | replicas: 3 13 | template: 14 | metadata: 15 | labels: 16 | app: guestbook 17 | tier: frontend 18 | spec: 19 | containers: 20 | - name: php-redis 21 | image: gcr.io/google-samples/gb-frontend:v4 22 | resources: 23 | requests: 24 | cpu: 100m 25 | memory: 100Mi 26 | env: 27 | - name: GET_HOSTS_FROM 28 | value: dns 29 | # Using `GET_HOSTS_FROM=dns` requires your cluster to 30 | # provide a dns service. As of Kubernetes 1.3, DNS is a built-in 31 | # service launched automatically. However, if the cluster you are using 32 | # does not have a built-in DNS service, you can instead 33 | # access an environment variable to find the master 34 | # service's host. To do so, comment out the 'value: dns' line above, and 35 | # uncomment the line below: 36 | # value: env 37 | ports: 38 | - containerPort: 80 39 | -------------------------------------------------------------------------------- /Chapter03/frontend-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: frontend 5 | labels: 6 | app: guestbook 7 | tier: frontend 8 | spec: 9 | # comment or delete the following line if you want to use a LoadBalancer 10 | #type: NodePort 11 | # if your cluster supports it, uncomment the following to automatically create 12 | # an external load-balanced IP for the frontend service. 13 | type: LoadBalancer 14 | ports: 15 | - port: 80 16 | selector: 17 | app: guestbook 18 | tier: frontend 19 | -------------------------------------------------------------------------------- /Chapter03/nillsf.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: redis-master-79bc96f6cf-cxgsd 5 | spec: 6 | containers: 7 | - name: basic 8 | image: k8s.gcr.io/redis:e2e -------------------------------------------------------------------------------- /Chapter03/redis-config: -------------------------------------------------------------------------------- 1 | maxmemory 2mb 2 | maxmemory-policy allkeys-lru -------------------------------------------------------------------------------- /Chapter03/redis-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | redis-config: |- 4 | maxmemory 2mb 5 | maxmemory-policy allkeys-lru 6 | kind: ConfigMap 7 | metadata: 8 | name: example-redis-config 9 | namespace: default 10 | -------------------------------------------------------------------------------- /Chapter03/redis-master-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: redis-master 5 | labels: 6 | app: redis 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: redis 11 | role: master 12 | tier: backend 13 | replicas: 1 14 | template: 15 | metadata: 16 | labels: 17 | app: redis 18 | role: master 19 | tier: backend 20 | spec: 21 | containers: 22 | - name: master 23 | image: k8s.gcr.io/redis:e2e # or just image: redis 24 | resources: 25 | requests: 26 | cpu: 100m 27 | memory: 100Mi 28 | ports: 29 | - containerPort: 6379 30 | -------------------------------------------------------------------------------- /Chapter03/redis-master-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: redis-master 5 | labels: 6 | app: redis 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: redis 11 | role: master 12 | tier: backend 13 | replicas: 1 14 | template: 15 | metadata: 16 | labels: 17 | app: redis 18 | role: master 19 | tier: backend 20 | spec: 21 | containers: 22 | - name: master 23 | image: k8s.gcr.io/redis:e2e # or just image: redis 24 | resources: 25 | requests: 26 | cpu: 100m 27 | memory: 100Mi 28 | ports: 29 | - containerPort: 6379 30 | -------------------------------------------------------------------------------- /Chapter03/redis-master-deployment_Modified.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: redis-master 5 | labels: 6 | app: redis 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: redis 11 | role: master 12 | tier: backend 13 | replicas: 1 14 | template: 15 | metadata: 16 | labels: 17 | app: redis 18 | role: master 19 | tier: backend 20 | spec: 21 | containers: 22 | - name: master 23 | image: k8s.gcr.io/redis:e2e 24 | command: 25 | - redis-server 26 | - "/redis-master/redis.conf" 27 | env: 28 | - name: MASTER 29 | value: "true" 30 | volumeMounts: 31 | - mountPath: /redis-master 32 | name: config 33 | resources: 34 | requests: 35 | cpu: 100m 36 | memory: 100Mi 37 | ports: 38 | - containerPort: 6379 39 | volumes: 40 | - name: config 41 | configMap: 42 | name: example-redis-config 43 | items: 44 | - key: redis-config 45 | path: redis.conf 46 | -------------------------------------------------------------------------------- /Chapter03/redis-master-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: redis-master 5 | labels: 6 | app: redis 7 | role: master 8 | tier: backend 9 | spec: 10 | ports: 11 | - port: 6379 12 | targetPort: 6379 13 | selector: 14 | app: redis 15 | role: master 16 | tier: backend -------------------------------------------------------------------------------- /Chapter03/redis-slave-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: redis-slave 5 | labels: 6 | app: redis 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: redis 11 | role: slave 12 | tier: backend 13 | replicas: 2 14 | template: 15 | metadata: 16 | labels: 17 | app: redis 18 | role: slave 19 | tier: backend 20 | spec: 21 | containers: 22 | - name: slave 23 | image: gcr.io/google_samples/gb-redisslave:v1 24 | resources: 25 | requests: 26 | cpu: 100m 27 | memory: 100Mi 28 | env: 29 | - name: GET_HOSTS_FROM 30 | value: dns 31 | # Using `GET_HOSTS_FROM=dns` requires your cluster to 32 | # provide a dns service. As of Kubernetes 1.3, DNS is a built-in 33 | # service launched automatically. However, if the cluster you are using 34 | # does not have a built-in DNS service, you can instead 35 | # access an environment variable to find the master 36 | # service's host. To do so, comment out the 'value: dns' line above, and 37 | # uncomment the line below: 38 | # value: env 39 | ports: 40 | - containerPort: 6379 41 | -------------------------------------------------------------------------------- /Chapter03/redis-slave-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: redis-slave 5 | labels: 6 | app: redis 7 | role: slave 8 | tier: backend 9 | spec: 10 | ports: 11 | - port: 6379 12 | selector: 13 | app: redis 14 | role: slave 15 | tier: backend 16 | -------------------------------------------------------------------------------- /Chapter04/frontend-image-patch.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | containers: 5 | - name: php-redis 6 | image: gcr.io/google-samples/gb-frontend:v3 -------------------------------------------------------------------------------- /Chapter04/frontend-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: guestbook 6 | tier: frontend 7 | name: frontend 8 | spec: 9 | ports: 10 | - port: 80 11 | protocol: TCP 12 | targetPort: 80 13 | selector: 14 | app: guestbook 15 | tier: frontend 16 | type: LoadBalancer 17 | -------------------------------------------------------------------------------- /Chapter04/hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2beta1 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: frontend-scaler 5 | spec: 6 | scaleTargetRef: 7 | apiVersion: extensions/v1beta1 8 | kind: Deployment 9 | name: frontend 10 | minReplicas: 1 11 | maxReplicas: 10 12 | metrics: 13 | - type: Resource 14 | resource: 15 | name: cpu 16 | targetAverageUtilization: 25 -------------------------------------------------------------------------------- /Chapter06/certificate-issuer-prod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1alpha2 2 | kind: Issuer 3 | metadata: 4 | name: letsencrypt-prod 5 | spec: 6 | acme: 7 | server: https://acme-v02.api.letsencrypt.org/directory 8 | email: 9 | privateKeySecretRef: 10 | name: letsencrypt-prod 11 | solvers: 12 | - http01: 13 | ingress: 14 | class: nginx 15 | -------------------------------------------------------------------------------- /Chapter06/certificate-issuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1alpha2 2 | kind: Issuer 3 | metadata: 4 | name: letsencrypt-staging 5 | spec: 6 | acme: 7 | server: https://acme-staging-v02.api.letsencrypt.org/directory 8 | email: 9 | privateKeySecretRef: 10 | name: letsencrypt-staging 11 | solvers: 12 | - http01: 13 | ingress: 14 | class: nginx -------------------------------------------------------------------------------- /Chapter06/frontend-oauth2-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: frontend-oauth2-ingress 5 | annotations: 6 | kubernetes.io/ingress.class: nginx 7 | nginx.ingress.kubernetes.io/auth-url: "http://oauth2-proxy.default.svc.cluster.local:4180/oauth2/auth" 8 | nginx.ingress.kubernetes.io/auth-signin: "http://..cloudapp.azure.com/oauth2/start" 9 | spec: 10 | rules: 11 | - host: ..cloudapp.azure.com 12 | http: 13 | paths: 14 | - path: / 15 | backend: 16 | serviceName: frontend 17 | servicePort: 80 -------------------------------------------------------------------------------- /Chapter06/ingress-with-tls-prod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: simple-frontend-ingress 5 | annotations: 6 | cert-manager.io/issuer: "letsencrypt-prod" 7 | spec: 8 | tls: 9 | - hosts: 10 | - ..cloudapp.azure.com 11 | secretName: frontend-tls 12 | rules: 13 | - host: ..cloudapp.azure.com 14 | http: 15 | paths: 16 | - path: / 17 | backend: 18 | serviceName: frontend 19 | servicePort: 80 -------------------------------------------------------------------------------- /Chapter06/ingress-with-tls.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: simple-frontend-ingress 5 | annotations: 6 | cert-manager.io/issuer: "letsencrypt-staging" 7 | spec: 8 | tls: 9 | - hosts: 10 | - ..cloudapp.azure.com 11 | secretName: frontend-tls 12 | rules: 13 | - host: ..cloudapp.azure.com 14 | http: 15 | paths: 16 | - path: / 17 | backend: 18 | serviceName: frontend 19 | servicePort: 80 -------------------------------------------------------------------------------- /Chapter06/oauth2_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: oauth2-proxy 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: oauth2-proxy 10 | template: 11 | metadata: 12 | labels: 13 | app: oauth2-proxy 14 | spec: 15 | containers: 16 | - env: 17 | - name: OAUTH2_PROXY_PROVIDER 18 | value: azure 19 | - name: OAUTH2_PROXY_AZURE_TENANT 20 | value: 21 | - name: OAUTH2_PROXY_CLIENT_ID 22 | value: 23 | - name: OAUTH2_PROXY_CLIENT_SECRET 24 | value: 25 | - name: OAUTH2_PROXY_COOKIE_SECRET 26 | value: somethingveryrandom 27 | - name: OAUTH2_PROXY_HTTP_ADDRESS 28 | value: "0.0.0.0:4180" 29 | - name: OAUTH2_PROXY_UPSTREAM 30 | value: "https://..cloudapp.azure.com/" 31 | - name: OAUTH2_PROXY_EMAIL_DOMAINS 32 | value: '*' 33 | image: quay.io/pusher/oauth2_proxy:latest 34 | imagePullPolicy: IfNotPresent 35 | name: oauth2-proxy 36 | ports: 37 | - containerPort: 4180 38 | protocol: TCP 39 | -------------------------------------------------------------------------------- /Chapter06/oauth2_ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: oauth2-proxy-ingress 5 | annotations: 6 | kubernetes.io/ingress.class: nginx 7 | cert-manager.io/issuer: "letsencrypt-prod" 8 | spec: 9 | tls: 10 | - hosts: 11 | - ..cloudapp.azure.com 12 | secretName: tls-secret 13 | rules: 14 | - host: ..cloudapp.azure.com 15 | http: 16 | paths: 17 | - path: /oauth2 18 | backend: 19 | serviceName: oauth2-proxy 20 | servicePort: 4180 -------------------------------------------------------------------------------- /Chapter06/oauth2_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: oauth2-proxy 5 | spec: 6 | ports: 7 | - name: http 8 | port: 4180 9 | protocol: TCP 10 | targetPort: 4180 11 | selector: 12 | app: oauth2-proxy -------------------------------------------------------------------------------- /Chapter06/simple-frontend-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: simple-frontend-ingress 5 | spec: 6 | rules: 7 | - http: 8 | paths: 9 | - path: / 10 | backend: 11 | serviceName: frontend 12 | servicePort: 80 -------------------------------------------------------------------------------- /Chapter07/healthy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | All is fine here 5 | 6 | 7 | OK 8 | 9 | 10 | -------------------------------------------------------------------------------- /Chapter07/index1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Server 1 5 | 6 | 7 | Server 1 8 | 9 | -------------------------------------------------------------------------------- /Chapter07/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Server 2 5 | 6 | 7 | Server 2 8 | 9 | -------------------------------------------------------------------------------- /Chapter07/testWeb.sh: -------------------------------------------------------------------------------- 1 | IP=$1 2 | for i in {1..50}; do curl --silent $IP | sed -n '7p' >> output.txt; done 3 | echo 'server 1: ';grep '1' output.txt | wc;echo 'server 2: ';grep '2' output.txt | wc 4 | rm output.txt -------------------------------------------------------------------------------- /Chapter07/webdeploy1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: server1 5 | labels: 6 | app: nginx 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | server: server1 12 | template: 13 | metadata: 14 | labels: 15 | app: web-server 16 | server: server1 17 | spec: 18 | containers: 19 | - name: nginx-1 20 | image: nginx 21 | ports: 22 | - containerPort: 80 23 | livenessProbe: 24 | httpGet: 25 | path: /healthy.html 26 | port: 80 27 | initialDelaySeconds: 3 28 | periodSeconds: 3 29 | readinessProbe: 30 | httpGet: 31 | path: /index.html 32 | port: 80 33 | initialDelaySeconds: 3 34 | periodSeconds: 3 35 | volumeMounts: 36 | - name: html 37 | mountPath: /usr/share/nginx/html 38 | - name: index 39 | mountPath: /tmp/index1.html 40 | subPath: index1.html 41 | - name: healthy 42 | mountPath: /tmp/healthy.html 43 | subPath: healthy.html 44 | command: ["/bin/sh", "-c"] 45 | args: ["cp /tmp/index1.html /usr/share/nginx/html/index.html; cp /tmp/healthy.html /usr/share/nginx/html/healthy.html; nginx; sleep inf"] 46 | volumes: 47 | - name: index 48 | configMap: 49 | name: server1 50 | - name: healthy 51 | configMap: 52 | name: healthy 53 | - name: html 54 | emptyDir: {} -------------------------------------------------------------------------------- /Chapter07/webdeploy2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: server2 5 | labels: 6 | app: nginx 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | server: server2 12 | template: 13 | metadata: 14 | labels: 15 | app: web-server 16 | server: server2 17 | spec: 18 | containers: 19 | - name: nginx-2 20 | image: nginx 21 | ports: 22 | - containerPort: 80 23 | livenessProbe: 24 | httpGet: 25 | path: /healthy.html 26 | port: 80 27 | initialDelaySeconds: 3 28 | periodSeconds: 3 29 | readinessProbe: 30 | httpGet: 31 | path: /index.html 32 | port: 80 33 | initialDelaySeconds: 3 34 | periodSeconds: 3 35 | volumeMounts: 36 | - name: html 37 | mountPath: /usr/share/nginx/html 38 | - name: index 39 | mountPath: /tmp/index2.html 40 | subPath: index2.html 41 | - name: healthy 42 | mountPath: /tmp/healthy.html 43 | subPath: healthy.html 44 | command: ["/bin/sh", "-c"] 45 | args: ["cp /tmp/index2.html /usr/share/nginx/html/index.html; cp /tmp/healthy.html /usr/share/nginx/html/healthy.html; nginx; sleep inf"] 46 | volumes: 47 | - name: index 48 | configMap: 49 | name: server2 50 | - name: healthy 51 | configMap: 52 | name: healthy 53 | - name: html 54 | emptyDir: {} -------------------------------------------------------------------------------- /Chapter07/webservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: web 5 | spec: 6 | selector: 7 | app: web-server 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 80 12 | type: LoadBalancer -------------------------------------------------------------------------------- /Chapter08/guestbook.php: -------------------------------------------------------------------------------- 1 | 'tcp', 24 | 'host' => $host, 25 | 'port' => 6379, 26 | ]); 27 | 28 | $client->set($_GET['key'], $_GET['value']); 29 | print('{"message": "Updated"}'); 30 | } else { 31 | $host = 'redis-slave'; 32 | if (getenv('GET_HOSTS_FROM') == 'env') { 33 | $host = getenv('REDIS_SLAVE_SERVICE_HOST'); 34 | } 35 | $client = new Predis\Client([ 36 | 'scheme' => 'tcp', 37 | 'host' => $host, 38 | 'port' => 6379, 39 | ]); 40 | 41 | $value = $client->get($_GET['key']); 42 | print('{"data": "' . $value . '"}'); 43 | } 44 | } else { 45 | phpinfo(); 46 | } ?> 47 | -------------------------------------------------------------------------------- /Chapter09/social-network/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !target/event-sourcing-microservices-basics.jar 3 | -------------------------------------------------------------------------------- /Chapter09/social-network/.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk11 4 | services: 5 | - docker 6 | env: 7 | matrix: 8 | - DOCKER_COMPOSE_VERSION=1.23.2 9 | before_deploy: 10 | - sudo rm /usr/local/bin/docker-compose 11 | - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname 12 | -s`-`uname -m` > docker-compose 13 | - chmod +x docker-compose 14 | - sudo mv docker-compose /usr/local/bin 15 | - sudo echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin 16 | deploy: 17 | provider: script 18 | script: bash deployment/docker/docker_push 19 | on: 20 | branch: master 21 | -------------------------------------------------------------------------------- /Chapter09/social-network/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-alpine 2 | 3 | RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64 && \ 4 | chmod 755 /usr/local/bin/dumb-init 5 | 6 | RUN mkdir -p /opt/app 7 | 8 | WORKDIR /opt/app 9 | 10 | EXPOSE 8080 11 | 12 | CMD ["-jar", "event-sourcing-microservices-basics.jar"] 13 | 14 | ENTRYPOINT ["/usr/local/bin/dumb-init", "java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-Xmx256m", "-Djava.security.egd=file:/dev/urandom"] 15 | 16 | COPY target/event-sourcing-microservices-basics.jar event-sourcing-microservices-basics.jar 17 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/docker/docker-compose-build.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | discovery-service: 6 | build: . 7 | image: hyperskale/discovery-service:0.0.1-SNAPSHOT 8 | 9 | edge-service: 10 | build: . 11 | image: hyperskale/edge-service:0.0.1-SNAPSHOT 12 | 13 | friend-service: 14 | build: . 15 | image: hyperskale/friend-service:0.0.2-SNAPSHOT 16 | 17 | user-service: 18 | build: . 19 | image: hyperskale/user-service:0.0.2-SNAPSHOT 20 | 21 | recommendation-service: 22 | build: . 23 | image: hyperskale/recommendation-service:0.0.1-SNAPSHOT 24 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/docker/docker_push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /usr/local/bin/docker-compose -f ./deployment/docker/docker-compose-build.yml push edge-service discovery-service friend-service user-service recommendation-service -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/.gitignore: -------------------------------------------------------------------------------- 1 | *.tgz 2 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/edge-service/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/edge-service/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "0.0.1-SNAPSHOT" 3 | description: Helm chart for the edge microservice in hyperskale/social-network 4 | name: edge-service 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/edge-service/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if contains "NodePort" .Values.service.type }} 3 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "edge-service.fullname" . }}) 4 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 5 | echo http://$NODE_IP:$NODE_PORT 6 | {{- else if contains "LoadBalancer" .Values.service.type }} 7 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 8 | You can watch the status of by running 'kubectl get svc -w {{ include "edge-service.fullname" . }}' 9 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "edge-service.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 10 | echo http://$SERVICE_IP:{{ .Values.service.port }} 11 | {{- else if contains "ClusterIP" .Values.service.type }} 12 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ include "edge-service.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 13 | echo "Visit http://127.0.0.1:8080 to use your application" 14 | kubectl port-forward $POD_NAME 8080:80 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/edge-service/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "edge-service.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "edge-service.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "edge-service.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create the name of the service account to use 36 | */}} 37 | {{- define "edge-service.serviceAccountName" -}} 38 | {{- if .Values.serviceAccount.create -}} 39 | {{ default (include "edge-service.fullname" .) .Values.serviceAccount.name }} 40 | {{- else -}} 41 | {{ default "default" .Values.serviceAccount.name }} 42 | {{- end -}} 43 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/edge-service/templates/cm-app.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.springConfig }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ include "edge-service.fullname" . }}-app 6 | labels: 7 | app: {{ include "edge-service.name" . }} 8 | chart: {{ include "edge-service.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | data: 12 | application.yaml: 13 | {{ toYaml .Values.SpringConfig | indent 4 }} 14 | {{ end }} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/edge-service/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "edge-service.fullname" . }} 5 | labels: 6 | app: {{ include "edge-service.name" . }} 7 | chart: {{ include "edge-service.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | replicas: {{ .Values.replicaCount }} 12 | selector: 13 | matchLabels: 14 | app: {{ include "edge-service.name" . }} 15 | release: {{ .Release.Name }} 16 | template: 17 | metadata: 18 | labels: 19 | app: {{ include "edge-service.name" . }} 20 | release: {{ .Release.Name }} 21 | annotations: 22 | prometheus.io/scrape: "true" 23 | prometheus.io/path: "/actuator/prometheus" 24 | spec: 25 | serviceAccountName: {{ template "edge-service.serviceAccountName" . }} 26 | {{ if .Values.springConfig }} 27 | volumes: 28 | - name: config-volume 29 | configMap: 30 | name: {{ include "edge-service.fullname" . }}-app 31 | {{ end }} 32 | containers: 33 | - name: {{ .Chart.Name }} 34 | {{ if .Values.springConfig }} 35 | volumeMounts: 36 | - name: config-volume 37 | mountPath: /config 38 | {{ end }} 39 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 40 | imagePullPolicy: {{ .Values.image.pullPolicy }} 41 | env: 42 | - name: SPRING_PROFILES_ACTIVE 43 | value: kubernetes 44 | - name: KUBERNETES_TRUST_CERTIFICATES 45 | value: "true" 46 | {{ if .Values.springConfig }} 47 | - name: SPRING_CONFIG_LOCATION 48 | value: "file:/config/application.yaml" 49 | {{ end }} 50 | ports: 51 | - name: http 52 | containerPort: 9000 53 | protocol: TCP 54 | livenessProbe: 55 | httpGet: 56 | path: /actuator/health 57 | port: http 58 | initialDelaySeconds: 300 59 | periodSeconds: 10 60 | readinessProbe: 61 | httpGet: 62 | path: /actuator/health 63 | port: http 64 | initialDelaySeconds: 30 65 | periodSeconds: 10 66 | resources: 67 | {{ toYaml .Values.resources | indent 12 }} 68 | {{- with .Values.nodeSelector }} 69 | nodeSelector: 70 | {{ toYaml . | indent 8 }} 71 | {{- end }} 72 | {{- with .Values.affinity }} 73 | affinity: 74 | {{ toYaml . | indent 8 }} 75 | {{- end }} 76 | {{- with .Values.tolerations }} 77 | tolerations: 78 | {{ toYaml . | indent 8 }} 79 | {{- end }} 80 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/edge-service/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ template "edge-service.fullname" . }} 6 | labels: 7 | app: {{ template "edge-service.name" . }} 8 | chart: {{ template "edge-service.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | rules: 12 | - apiGroups: [""] 13 | resources: ["services", "pods", "endpoints", "configmaps"] 14 | verbs: ["get","list"] 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1beta1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: {{ template "edge-service.fullname" . }} 20 | labels: 21 | app: {{ template "edge-service.name" . }} 22 | chart: {{ template "edge-service.chart" . }} 23 | release: {{ .Release.Name }} 24 | heritage: {{ .Release.Service }} 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: {{ template "edge-service.fullname" . }} 29 | subjects: 30 | - name: {{ template "edge-service.serviceAccountName" . }} 31 | namespace: {{ .Release.Namespace | quote }} 32 | kind: ServiceAccount 33 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/edge-service/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "edge-service.fullname" . }} 5 | labels: 6 | app: {{ include "edge-service.name" . }} 7 | chart: {{ include "edge-service.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - name: http 14 | targetPort: http 15 | protocol: TCP 16 | port: {{ .Values.service.port }} 17 | selector: 18 | app: {{ include "edge-service.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/edge-service/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "edge-service.serviceAccountName" . }} 6 | namespace: {{ .Release.Namespace | quote }} 7 | labels: 8 | app: {{ template "edge-service.name" . }} 9 | chart: {{ template "edge-service.chart" . }} 10 | release: {{ .Release.Name }} 11 | heritage: {{ .Release.Service }} 12 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/edge-service/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for discovery-service. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: hyperskale/edge-service 9 | tag: 0.0.1-SNAPSHOT 10 | pullPolicy: Always 11 | 12 | nameOverride: "" 13 | fullnameOverride: "" 14 | 15 | service: 16 | type: ClusterIP 17 | port: 9000 18 | 19 | serviceAccount: 20 | create: true 21 | name: ~ 22 | 23 | rbac: 24 | create: true 25 | 26 | # inject extra spring into app 27 | #springConfig: 28 | # spring: 29 | # profiles: kubernetes 30 | # cloud: 31 | # gateway: 32 | 33 | resources: 34 | # We usually recommend not to specify default resources and to leave this as a conscious 35 | # choice for the user. This also increases chances charts run on environments with little 36 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 37 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 38 | # limits: 39 | # cpu: 100m 40 | # memory: 128Mi 41 | requests: 42 | # cpu: 100m 43 | memory: 225Mi 44 | 45 | nodeSelector: {} 46 | 47 | tolerations: [] 48 | 49 | affinity: {} 50 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/friend-service/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/friend-service/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "0.0.2-SNAPSHOT" 3 | description: Helm chart for the friend microservice in hyperskale/social-network 4 | name: friend-service 5 | version: 0.2.0 6 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/friend-service/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://kubernetes-charts.storage.googleapis.com/ 4 | version: 3.1.4 5 | digest: sha256:f33715012341b011cc243f722b688e091244b161a1e6c82f73572d12ff2f8b03 6 | generated: "2020-02-16T23:09:48.520797337Z" 7 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/friend-service/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | version: 3.1.4 4 | repository: https://kubernetes-charts.storage.googleapis.com/ 5 | condition: postgresql.enabled 6 | tags: 7 | - friend-db -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/friend-service/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if contains "NodePort" .Values.service.type }} 3 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "friend-service.fullname" . }}) 4 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 5 | echo http://$NODE_IP:$NODE_PORT 6 | {{- else if contains "LoadBalancer" .Values.service.type }} 7 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 8 | You can watch the status of by running 'kubectl get svc -w {{ include "friend-service.fullname" . }}' 9 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "friend-service.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 10 | echo http://$SERVICE_IP:{{ .Values.service.port }} 11 | {{- else if contains "ClusterIP" .Values.service.type }} 12 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ include "friend-service.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 13 | echo "Visit http://127.0.0.1:8080 to use your application" 14 | kubectl port-forward $POD_NAME 8080:80 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/friend-service/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "friend-service.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "friend-service.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "friend-service.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create the name of the service account to use 36 | */}} 37 | {{- define "friend-service.serviceAccountName" -}} 38 | {{- if .Values.serviceAccount.create -}} 39 | {{ default (include "friend-service.fullname" .) .Values.serviceAccount.name }} 40 | {{- else -}} 41 | {{ default "default" .Values.serviceAccount.name }} 42 | {{- end -}} 43 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/friend-service/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ template "friend-service.fullname" . }} 6 | labels: 7 | app: {{ template "friend-service.name" . }} 8 | chart: {{ template "friend-service.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | rules: 12 | - apiGroups: [""] 13 | resources: ["services", "pods", "endpoints", "configmaps"] 14 | verbs: ["get","list"] 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1beta1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: {{ template "friend-service.fullname" . }} 20 | labels: 21 | app: {{ template "friend-service.name" . }} 22 | chart: {{ template "friend-service.chart" . }} 23 | release: {{ .Release.Name }} 24 | heritage: {{ .Release.Service }} 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: {{ template "friend-service.fullname" . }} 29 | subjects: 30 | - name: {{ template "friend-service.serviceAccountName" . }} 31 | namespace: {{ .Release.Namespace | quote }} 32 | kind: ServiceAccount 33 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/friend-service/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "friend-service.fullname" . }} 5 | labels: 6 | app: {{ include "friend-service.name" . }} 7 | chart: {{ include "friend-service.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: http 15 | protocol: TCP 16 | name: http 17 | selector: 18 | app: {{ include "friend-service.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/friend-service/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "friend-service.serviceAccountName" . }} 6 | namespace: {{ .Release.Namespace | quote }} 7 | labels: 8 | app: {{ template "friend-service.name" . }} 9 | chart: {{ template "friend-service.chart" . }} 10 | release: {{ .Release.Name }} 11 | heritage: {{ .Release.Service }} 12 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/friend-service/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for discovery-service. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | kafka: 7 | enabled: "" 8 | eventhub: 9 | name: "" 10 | connection: "" 11 | 12 | image: 13 | repository: hyperskale/friend-service 14 | tag: 0.0.2-SNAPSHOT 15 | pullPolicy: Always 16 | 17 | nameOverride: "" 18 | fullnameOverride: "" 19 | 20 | service: 21 | type: ClusterIP 22 | port: 8100 23 | 24 | serviceAccount: 25 | create: true 26 | # name: ~ 27 | 28 | rbac: 29 | create: true 30 | 31 | resources: 32 | # We usually recommend not to specify default resources and to leave this as a conscious 33 | # choice for the user. This also increases chances charts run on environments with little 34 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 35 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 36 | #limits: 37 | # cpu: 100m 38 | # memory: 128Mi 39 | requests: 40 | # cpu: 100m 41 | memory: 275Mi 42 | 43 | nodeSelector: {} 44 | 45 | tolerations: [] 46 | 47 | affinity: {} 48 | 49 | postgresql: 50 | enabled: true 51 | nameOverride: friend-db 52 | fullnameOverride: friend-db 53 | postgresqlPassword: example 54 | persistence: 55 | enabled: false 56 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/recommendation-service/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/recommendation-service/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "0.0.1-SNAPSHOT" 3 | description: Helm chart for the recommendation microservice in hyperskale/social-network 4 | name: recommendation-service 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/recommendation-service/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: neo4j 3 | repository: https://kubernetes-charts.storage.googleapis.com/ 4 | version: 0.8.0 5 | digest: sha256:ce0d3e6fccfc36b743f6de4f3a8cecbd9439e5893d15af46d64cfab4a0983718 6 | generated: "2020-02-16T23:10:02.707649004Z" 7 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/recommendation-service/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: neo4j 3 | version: 0.8.0 4 | repository: https://kubernetes-charts.storage.googleapis.com/ 5 | condition: neo4j.enabled 6 | tags: 7 | - neo4j -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/recommendation-service/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if contains "NodePort" .Values.service.type }} 3 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "recommendation-service.fullname" . }}) 4 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 5 | echo http://$NODE_IP:$NODE_PORT 6 | {{- else if contains "LoadBalancer" .Values.service.type }} 7 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 8 | You can watch the status of by running 'kubectl get svc -w {{ include "recommendation-service.fullname" . }}' 9 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "recommendation-service.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 10 | echo http://$SERVICE_IP:{{ .Values.service.port }} 11 | {{- else if contains "ClusterIP" .Values.service.type }} 12 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ include "recommendation-service.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 13 | echo "Visit http://127.0.0.1:8080 to use your application" 14 | kubectl port-forward $POD_NAME 8080:80 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/recommendation-service/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "recommendation-service.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "recommendation-service.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "recommendation-service.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create the name of the service account to use 36 | */}} 37 | {{- define "recommendation-service.serviceAccountName" -}} 38 | {{- if .Values.serviceAccount.create -}} 39 | {{ default (include "recommendation-service.fullname" .) .Values.serviceAccount.name }} 40 | {{- else -}} 41 | {{ default "default" .Values.serviceAccount.name }} 42 | {{- end -}} 43 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/recommendation-service/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ template "recommendation-service.fullname" . }} 6 | labels: 7 | app: {{ template "recommendation-service.name" . }} 8 | chart: {{ template "recommendation-service.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | rules: 12 | - apiGroups: [""] 13 | resources: ["services", "pods", "endpoints", "configmaps"] 14 | verbs: ["get","list"] 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1beta1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: {{ template "recommendation-service.fullname" . }} 20 | labels: 21 | app: {{ template "recommendation-service.name" . }} 22 | chart: {{ template "recommendation-service.chart" . }} 23 | release: {{ .Release.Name }} 24 | heritage: {{ .Release.Service }} 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: {{ template "recommendation-service.fullname" . }} 29 | subjects: 30 | - name: {{ template "recommendation-service.serviceAccountName" . }} 31 | namespace: {{ .Release.Namespace | quote }} 32 | kind: ServiceAccount 33 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/recommendation-service/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "recommendation-service.fullname" . }} 5 | labels: 6 | app: {{ include "recommendation-service.name" . }} 7 | chart: {{ include "recommendation-service.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: http 15 | protocol: TCP 16 | name: http 17 | selector: 18 | app: {{ include "recommendation-service.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/recommendation-service/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "recommendation-service.serviceAccountName" . }} 6 | namespace: {{ .Release.Namespace | quote }} 7 | labels: 8 | app: {{ template "recommendation-service.name" . }} 9 | chart: {{ template "recommendation-service.chart" . }} 10 | release: {{ .Release.Name }} 11 | heritage: {{ .Release.Service }} 12 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/recommendation-service/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for discovery-service. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | kafka: 7 | enabled: "" 8 | eventhub: 9 | name: "" 10 | connection: "" 11 | image: 12 | repository: hyperskale/recommendation-service 13 | tag: 0.0.1-SNAPSHOT 14 | pullPolicy: Always 15 | 16 | nameOverride: "" 17 | fullnameOverride: "" 18 | 19 | service: 20 | type: ClusterIP 21 | port: 8110 22 | 23 | serviceAccount: 24 | create: true 25 | # name: ~ 26 | 27 | rbac: 28 | create: true 29 | 30 | resources: 31 | # We usually recommend not to specify default resources and to leave this as a conscious 32 | # choice for the user. This also increases chances charts run on environments with little 33 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 34 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 35 | # limits: 36 | # cpu: 100m 37 | # memory: 128Mi 38 | requests: 39 | # cpu: 100m 40 | memory: 325Mi 41 | 42 | nodeSelector: {} 43 | 44 | tolerations: [] 45 | 46 | affinity: {} 47 | 48 | neo4j: 49 | enabled: true 50 | image: neo4j 51 | imageTag: 3.5.0 52 | authEnabled: false 53 | nameOverride: neo4j 54 | acceptLicenseAgreement: "yes" 55 | core: 56 | numberOfServers: 1 57 | resources: 58 | requests: 59 | memory: 750Mi 60 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "0.0.1-SNAPSHOT" 3 | description: A Helm chart for Hyerskale Social Network 4 | name: social-network 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/README.md: -------------------------------------------------------------------------------- 1 | # Deploy Hyerskale Social Network via Helm 2 | 3 | ## Prerequisites 4 | 5 | * A Kubernetes Cluster 6 | * [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl) 7 | * [helm](https://docs.helm.sh/using_helm/#installing-helm) 8 | 9 | Ensure you have access to Kubernetes: 10 | 11 | ```bash 12 | kubectl get nodes 13 | NAME STATUS ROLES AGE VERSION 14 | vm-0acae492-fe57-46b8-510f-f95b7b007078 Ready 7d v1.11.5 15 | vm-0f68c2fd-cff9-4c23-4387-2373bd2b4ae3 Ready 7d v1.11.5 16 | vm-ab234512-d336-4f5a-495a-a917657575c1 Ready 7d v1.11.5 17 | ``` 18 | 19 | Install helm tiller (RBAC): 20 | 21 | ```bash 22 | kubectl -n kube-system create serviceaccount tiller 23 | kubectl create clusterrolebinding tiller \ 24 | --clusterrole cluster-admin \ 25 | --serviceaccount=kube-system:tiller 26 | helm init --service-account=tiller 27 | ``` 28 | 29 | Clone this repo locally: 30 | 31 | ```bash 32 | git clone https://github.com/hyperskale/social-network-example.git 33 | cd social-network-example 34 | ``` 35 | 36 | Add the bitnami helm repository which contains the `kafka` and `zookeeper` charts: 37 | 38 | ```bash 39 | helm repo add bitnami https://charts.bitnami.com 40 | helm repo add incubator https://kubernetes-charts-incubator.storage.googleapis.com 41 | ``` 42 | 43 | ## Update Dependencies 44 | 45 | Run a few helm commands to ensure all dependent charts are available: 46 | 47 | ```bash 48 | helm dep update deployment/helm/social-network 49 | helm dep update deployment/helm/friend-service 50 | helm dep update deployment/helm/user-service 51 | helm dep update deployment/helm/recommendation-service 52 | 53 | ``` 54 | 55 | ## Deploy 56 | 57 | Once Helm is set up, deploying this is quite simple: 58 | 59 | ```bash 60 | helm install --namespace social-network --name social-network --set fullNameOverride=social-network \ 61 | deployment/helm/social-network 62 | ``` 63 | 64 | There is no security on the apps, therefore rather than exposing the edge 65 | to the internet we can utilize `kubectl port-forward` to access the app: 66 | 67 | ```bash 68 | kubectl --namespace social-network port-forward svc/edge-service 9000 69 | ``` 70 | 71 | run the following script to add users and friendships to the social network: 72 | 73 | ```bash 74 | bash deployment/sbin/generate-social-network.sh 75 | ``` 76 | 77 | ## Cleanup 78 | 79 | To uninstall run the following commands: 80 | 81 | ```bash 82 | helm delete --purge social-network 83 | kubectl delete pvc datadir-social-network-neo4j-core-0 84 | ``` -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/edge-service/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/edge-service/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "0.0.1-SNAPSHOT" 3 | description: Helm chart for the edge microservice in hyperskale/social-network 4 | name: edge-service 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/edge-service/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if contains "NodePort" .Values.service.type }} 3 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "edge-service.fullname" . }}) 4 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 5 | echo http://$NODE_IP:$NODE_PORT 6 | {{- else if contains "LoadBalancer" .Values.service.type }} 7 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 8 | You can watch the status of by running 'kubectl get svc -w {{ include "edge-service.fullname" . }}' 9 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "edge-service.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 10 | echo http://$SERVICE_IP:{{ .Values.service.port }} 11 | {{- else if contains "ClusterIP" .Values.service.type }} 12 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ include "edge-service.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 13 | echo "Visit http://127.0.0.1:8080 to use your application" 14 | kubectl port-forward $POD_NAME 8080:80 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/edge-service/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "edge-service.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "edge-service.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "edge-service.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create the name of the service account to use 36 | */}} 37 | {{- define "edge-service.serviceAccountName" -}} 38 | {{- if .Values.serviceAccount.create -}} 39 | {{ default (include "edge-service.fullname" .) .Values.serviceAccount.name }} 40 | {{- else -}} 41 | {{ default "default" .Values.serviceAccount.name }} 42 | {{- end -}} 43 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/edge-service/templates/cm-app.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.springConfig }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ include "edge-service.fullname" . }}-app 6 | labels: 7 | app: {{ include "edge-service.name" . }} 8 | chart: {{ include "edge-service.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | data: 12 | application.yaml: 13 | {{ toYaml .Values.SpringConfig | indent 4 }} 14 | {{ end }} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/edge-service/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "edge-service.fullname" . }} 5 | labels: 6 | app: {{ include "edge-service.name" . }} 7 | chart: {{ include "edge-service.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | replicas: {{ .Values.replicaCount }} 12 | selector: 13 | matchLabels: 14 | app: {{ include "edge-service.name" . }} 15 | release: {{ .Release.Name }} 16 | template: 17 | metadata: 18 | labels: 19 | app: {{ include "edge-service.name" . }} 20 | release: {{ .Release.Name }} 21 | annotations: 22 | prometheus.io/scrape: "true" 23 | prometheus.io/path: "/actuator/prometheus" 24 | spec: 25 | serviceAccountName: {{ template "edge-service.serviceAccountName" . }} 26 | {{ if .Values.springConfig }} 27 | volumes: 28 | - name: config-volume 29 | configMap: 30 | name: {{ include "edge-service.fullname" . }}-app 31 | {{ end }} 32 | containers: 33 | - name: {{ .Chart.Name }} 34 | {{ if .Values.springConfig }} 35 | volumeMounts: 36 | - name: config-volume 37 | mountPath: /config 38 | {{ end }} 39 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 40 | imagePullPolicy: {{ .Values.image.pullPolicy }} 41 | env: 42 | - name: SPRING_PROFILES_ACTIVE 43 | value: kubernetes 44 | - name: KUBERNETES_TRUST_CERTIFICATES 45 | value: "true" 46 | {{ if .Values.springConfig }} 47 | - name: SPRING_CONFIG_LOCATION 48 | value: "file:/config/application.yaml" 49 | {{ end }} 50 | ports: 51 | - name: http 52 | containerPort: 9000 53 | protocol: TCP 54 | livenessProbe: 55 | httpGet: 56 | path: /actuator/health 57 | port: http 58 | initialDelaySeconds: 300 59 | periodSeconds: 10 60 | readinessProbe: 61 | httpGet: 62 | path: /actuator/health 63 | port: http 64 | initialDelaySeconds: 30 65 | periodSeconds: 10 66 | resources: 67 | {{ toYaml .Values.resources | indent 12 }} 68 | {{- with .Values.nodeSelector }} 69 | nodeSelector: 70 | {{ toYaml . | indent 8 }} 71 | {{- end }} 72 | {{- with .Values.affinity }} 73 | affinity: 74 | {{ toYaml . | indent 8 }} 75 | {{- end }} 76 | {{- with .Values.tolerations }} 77 | tolerations: 78 | {{ toYaml . | indent 8 }} 79 | {{- end }} 80 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/edge-service/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ template "edge-service.fullname" . }} 6 | labels: 7 | app: {{ template "edge-service.name" . }} 8 | chart: {{ template "edge-service.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | rules: 12 | - apiGroups: [""] 13 | resources: ["services", "pods", "endpoints", "configmaps"] 14 | verbs: ["get","list"] 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1beta1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: {{ template "edge-service.fullname" . }} 20 | labels: 21 | app: {{ template "edge-service.name" . }} 22 | chart: {{ template "edge-service.chart" . }} 23 | release: {{ .Release.Name }} 24 | heritage: {{ .Release.Service }} 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: {{ template "edge-service.fullname" . }} 29 | subjects: 30 | - name: {{ template "edge-service.serviceAccountName" . }} 31 | namespace: {{ .Release.Namespace | quote }} 32 | kind: ServiceAccount 33 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/edge-service/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "edge-service.fullname" . }} 5 | labels: 6 | app: {{ include "edge-service.name" . }} 7 | chart: {{ include "edge-service.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - name: http 14 | targetPort: http 15 | protocol: TCP 16 | port: {{ .Values.service.port }} 17 | selector: 18 | app: {{ include "edge-service.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/edge-service/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "edge-service.serviceAccountName" . }} 6 | namespace: {{ .Release.Namespace | quote }} 7 | labels: 8 | app: {{ template "edge-service.name" . }} 9 | chart: {{ template "edge-service.chart" . }} 10 | release: {{ .Release.Name }} 11 | heritage: {{ .Release.Service }} 12 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/edge-service/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for discovery-service. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: hyperskale/edge-service 9 | tag: 0.0.1-SNAPSHOT 10 | pullPolicy: Always 11 | 12 | nameOverride: "" 13 | fullnameOverride: "" 14 | 15 | service: 16 | type: ClusterIP 17 | port: 9000 18 | 19 | serviceAccount: 20 | create: true 21 | name: ~ 22 | 23 | rbac: 24 | create: true 25 | 26 | # inject extra spring into app 27 | #springConfig: 28 | # spring: 29 | # profiles: kubernetes 30 | # cloud: 31 | # gateway: 32 | 33 | resources: 34 | # We usually recommend not to specify default resources and to leave this as a conscious 35 | # choice for the user. This also increases chances charts run on environments with little 36 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 37 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 38 | # limits: 39 | # cpu: 100m 40 | # memory: 128Mi 41 | requests: 42 | # cpu: 100m 43 | memory: 225Mi 44 | 45 | nodeSelector: {} 46 | 47 | tolerations: [] 48 | 49 | affinity: {} 50 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/friend-service/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/friend-service/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "0.0.2-SNAPSHOT" 3 | description: Helm chart for the friend microservice in hyperskale/social-network 4 | name: friend-service 5 | version: 0.2.0 6 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/friend-service/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://kubernetes-charts.storage.googleapis.com/ 4 | version: 3.1.4 5 | digest: sha256:f33715012341b011cc243f722b688e091244b161a1e6c82f73572d12ff2f8b03 6 | generated: "2020-02-16T23:09:48.520797337Z" 7 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/friend-service/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | version: 3.1.4 4 | repository: https://kubernetes-charts.storage.googleapis.com/ 5 | condition: postgresql.enabled 6 | tags: 7 | - friend-db -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/friend-service/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if contains "NodePort" .Values.service.type }} 3 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "friend-service.fullname" . }}) 4 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 5 | echo http://$NODE_IP:$NODE_PORT 6 | {{- else if contains "LoadBalancer" .Values.service.type }} 7 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 8 | You can watch the status of by running 'kubectl get svc -w {{ include "friend-service.fullname" . }}' 9 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "friend-service.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 10 | echo http://$SERVICE_IP:{{ .Values.service.port }} 11 | {{- else if contains "ClusterIP" .Values.service.type }} 12 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ include "friend-service.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 13 | echo "Visit http://127.0.0.1:8080 to use your application" 14 | kubectl port-forward $POD_NAME 8080:80 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/friend-service/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "friend-service.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "friend-service.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "friend-service.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create the name of the service account to use 36 | */}} 37 | {{- define "friend-service.serviceAccountName" -}} 38 | {{- if .Values.serviceAccount.create -}} 39 | {{ default (include "friend-service.fullname" .) .Values.serviceAccount.name }} 40 | {{- else -}} 41 | {{ default "default" .Values.serviceAccount.name }} 42 | {{- end -}} 43 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/friend-service/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ template "friend-service.fullname" . }} 6 | labels: 7 | app: {{ template "friend-service.name" . }} 8 | chart: {{ template "friend-service.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | rules: 12 | - apiGroups: [""] 13 | resources: ["services", "pods", "endpoints", "configmaps"] 14 | verbs: ["get","list"] 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1beta1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: {{ template "friend-service.fullname" . }} 20 | labels: 21 | app: {{ template "friend-service.name" . }} 22 | chart: {{ template "friend-service.chart" . }} 23 | release: {{ .Release.Name }} 24 | heritage: {{ .Release.Service }} 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: {{ template "friend-service.fullname" . }} 29 | subjects: 30 | - name: {{ template "friend-service.serviceAccountName" . }} 31 | namespace: {{ .Release.Namespace | quote }} 32 | kind: ServiceAccount 33 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/friend-service/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "friend-service.fullname" . }} 5 | labels: 6 | app: {{ include "friend-service.name" . }} 7 | chart: {{ include "friend-service.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: http 15 | protocol: TCP 16 | name: http 17 | selector: 18 | app: {{ include "friend-service.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/friend-service/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "friend-service.serviceAccountName" . }} 6 | namespace: {{ .Release.Namespace | quote }} 7 | labels: 8 | app: {{ template "friend-service.name" . }} 9 | chart: {{ template "friend-service.chart" . }} 10 | release: {{ .Release.Name }} 11 | heritage: {{ .Release.Service }} 12 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/friend-service/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for discovery-service. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | kafka: 7 | enabled: "" 8 | eventhub: 9 | name: "" 10 | connection: "" 11 | 12 | image: 13 | repository: hyperskale/friend-service 14 | tag: 0.0.2-SNAPSHOT 15 | pullPolicy: Always 16 | 17 | nameOverride: "" 18 | fullnameOverride: "" 19 | 20 | service: 21 | type: ClusterIP 22 | port: 8100 23 | 24 | serviceAccount: 25 | create: true 26 | # name: ~ 27 | 28 | rbac: 29 | create: true 30 | 31 | resources: 32 | # We usually recommend not to specify default resources and to leave this as a conscious 33 | # choice for the user. This also increases chances charts run on environments with little 34 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 35 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 36 | #limits: 37 | # cpu: 100m 38 | # memory: 128Mi 39 | requests: 40 | # cpu: 100m 41 | memory: 275Mi 42 | 43 | nodeSelector: {} 44 | 45 | tolerations: [] 46 | 47 | affinity: {} 48 | 49 | postgresql: 50 | enabled: true 51 | nameOverride: friend-db 52 | fullnameOverride: friend-db 53 | postgresqlPassword: example 54 | persistence: 55 | enabled: false 56 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/recommendation-service/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/recommendation-service/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "0.0.1-SNAPSHOT" 3 | description: Helm chart for the recommendation microservice in hyperskale/social-network 4 | name: recommendation-service 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/recommendation-service/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: neo4j 3 | repository: https://kubernetes-charts.storage.googleapis.com/ 4 | version: 0.8.0 5 | digest: sha256:ce0d3e6fccfc36b743f6de4f3a8cecbd9439e5893d15af46d64cfab4a0983718 6 | generated: "2020-02-16T23:10:02.707649004Z" 7 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/recommendation-service/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: neo4j 3 | version: 0.8.0 4 | repository: https://kubernetes-charts.storage.googleapis.com/ 5 | condition: neo4j.enabled 6 | tags: 7 | - neo4j -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/recommendation-service/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if contains "NodePort" .Values.service.type }} 3 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "recommendation-service.fullname" . }}) 4 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 5 | echo http://$NODE_IP:$NODE_PORT 6 | {{- else if contains "LoadBalancer" .Values.service.type }} 7 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 8 | You can watch the status of by running 'kubectl get svc -w {{ include "recommendation-service.fullname" . }}' 9 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "recommendation-service.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 10 | echo http://$SERVICE_IP:{{ .Values.service.port }} 11 | {{- else if contains "ClusterIP" .Values.service.type }} 12 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ include "recommendation-service.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 13 | echo "Visit http://127.0.0.1:8080 to use your application" 14 | kubectl port-forward $POD_NAME 8080:80 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/recommendation-service/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "recommendation-service.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "recommendation-service.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "recommendation-service.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create the name of the service account to use 36 | */}} 37 | {{- define "recommendation-service.serviceAccountName" -}} 38 | {{- if .Values.serviceAccount.create -}} 39 | {{ default (include "recommendation-service.fullname" .) .Values.serviceAccount.name }} 40 | {{- else -}} 41 | {{ default "default" .Values.serviceAccount.name }} 42 | {{- end -}} 43 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/recommendation-service/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ template "recommendation-service.fullname" . }} 6 | labels: 7 | app: {{ template "recommendation-service.name" . }} 8 | chart: {{ template "recommendation-service.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | rules: 12 | - apiGroups: [""] 13 | resources: ["services", "pods", "endpoints", "configmaps"] 14 | verbs: ["get","list"] 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1beta1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: {{ template "recommendation-service.fullname" . }} 20 | labels: 21 | app: {{ template "recommendation-service.name" . }} 22 | chart: {{ template "recommendation-service.chart" . }} 23 | release: {{ .Release.Name }} 24 | heritage: {{ .Release.Service }} 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: {{ template "recommendation-service.fullname" . }} 29 | subjects: 30 | - name: {{ template "recommendation-service.serviceAccountName" . }} 31 | namespace: {{ .Release.Namespace | quote }} 32 | kind: ServiceAccount 33 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/recommendation-service/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "recommendation-service.fullname" . }} 5 | labels: 6 | app: {{ include "recommendation-service.name" . }} 7 | chart: {{ include "recommendation-service.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: http 15 | protocol: TCP 16 | name: http 17 | selector: 18 | app: {{ include "recommendation-service.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/recommendation-service/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "recommendation-service.serviceAccountName" . }} 6 | namespace: {{ .Release.Namespace | quote }} 7 | labels: 8 | app: {{ template "recommendation-service.name" . }} 9 | chart: {{ template "recommendation-service.chart" . }} 10 | release: {{ .Release.Name }} 11 | heritage: {{ .Release.Service }} 12 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/recommendation-service/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for discovery-service. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | kafka: 7 | enabled: "" 8 | eventhub: 9 | name: "" 10 | connection: "" 11 | image: 12 | repository: hyperskale/recommendation-service 13 | tag: 0.0.1-SNAPSHOT 14 | pullPolicy: Always 15 | 16 | nameOverride: "" 17 | fullnameOverride: "" 18 | 19 | service: 20 | type: ClusterIP 21 | port: 8110 22 | 23 | serviceAccount: 24 | create: true 25 | # name: ~ 26 | 27 | rbac: 28 | create: true 29 | 30 | resources: 31 | # We usually recommend not to specify default resources and to leave this as a conscious 32 | # choice for the user. This also increases chances charts run on environments with little 33 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 34 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 35 | # limits: 36 | # cpu: 100m 37 | # memory: 128Mi 38 | requests: 39 | # cpu: 100m 40 | memory: 325Mi 41 | 42 | nodeSelector: {} 43 | 44 | tolerations: [] 45 | 46 | affinity: {} 47 | 48 | neo4j: 49 | enabled: true 50 | image: neo4j 51 | imageTag: 3.5.0 52 | authEnabled: false 53 | nameOverride: neo4j 54 | acceptLicenseAgreement: "yes" 55 | core: 56 | numberOfServers: 1 57 | resources: 58 | requests: 59 | memory: 750Mi 60 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/user-service/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/user-service/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "0.0.2-SNAPSHOT" 3 | description: Helm chart for the user microservice in hyperskale/social-network 4 | name: user-service 5 | version: 0.2.0 6 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/user-service/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://kubernetes-charts.storage.googleapis.com/ 4 | version: 3.1.4 5 | digest: sha256:f33715012341b011cc243f722b688e091244b161a1e6c82f73572d12ff2f8b03 6 | generated: "2020-02-16T23:09:55.636759446Z" 7 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/user-service/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | version: 3.1.4 4 | repository: https://kubernetes-charts.storage.googleapis.com/ 5 | condition: postgresql.enabled 6 | tags: 7 | - friend-db -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/user-service/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if contains "NodePort" .Values.service.type }} 3 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "user-service.fullname" . }}) 4 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 5 | echo http://$NODE_IP:$NODE_PORT 6 | {{- else if contains "LoadBalancer" .Values.service.type }} 7 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 8 | You can watch the status of by running 'kubectl get svc -w {{ include "user-service.fullname" . }}' 9 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "user-service.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 10 | echo http://$SERVICE_IP:{{ .Values.service.port }} 11 | {{- else if contains "ClusterIP" .Values.service.type }} 12 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ include "user-service.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 13 | echo "Visit http://127.0.0.1:8080 to use your application" 14 | kubectl port-forward $POD_NAME 8080:80 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/user-service/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "user-service.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "user-service.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "user-service.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create the name of the service account to use 36 | */}} 37 | {{- define "user-service.serviceAccountName" -}} 38 | {{- if .Values.serviceAccount.create -}} 39 | {{ default (include "user-service.fullname" .) .Values.serviceAccount.name }} 40 | {{- else -}} 41 | {{ default "default" .Values.serviceAccount.name }} 42 | {{- end -}} 43 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/user-service/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ template "user-service.fullname" . }} 6 | labels: 7 | app: {{ template "user-service.name" . }} 8 | chart: {{ template "user-service.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | rules: 12 | - apiGroups: [""] 13 | resources: ["services", "pods", "endpoints", "configmaps"] 14 | verbs: ["get","list"] 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1beta1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: {{ template "user-service.fullname" . }} 20 | labels: 21 | app: {{ template "user-service.name" . }} 22 | chart: {{ template "user-service.chart" . }} 23 | release: {{ .Release.Name }} 24 | heritage: {{ .Release.Service }} 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: {{ template "user-service.fullname" . }} 29 | subjects: 30 | - name: {{ template "user-service.serviceAccountName" . }} 31 | namespace: {{ .Release.Namespace | quote }} 32 | kind: ServiceAccount 33 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/user-service/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "user-service.fullname" . }} 5 | labels: 6 | app: {{ include "user-service.name" . }} 7 | chart: {{ include "user-service.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: http 15 | protocol: TCP 16 | name: http 17 | selector: 18 | app: {{ include "user-service.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/user-service/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "user-service.serviceAccountName" . }} 6 | namespace: {{ .Release.Namespace | quote }} 7 | labels: 8 | app: {{ template "user-service.name" . }} 9 | chart: {{ template "user-service.chart" . }} 10 | release: {{ .Release.Name }} 11 | heritage: {{ .Release.Service }} 12 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/charts/user-service/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for discovery-service. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | kafka: 7 | enabled: true 8 | eventhub: 9 | name: "" 10 | connection: "" 11 | image: 12 | repository: hyperskale/user-service 13 | tag: 0.0.2-SNAPSHOT 14 | pullPolicy: Always 15 | 16 | nameOverride: "" 17 | fullnameOverride: "" 18 | 19 | service: 20 | type: ClusterIP 21 | port: 8120 22 | 23 | serviceAccount: 24 | create: true 25 | # name: ~ 26 | 27 | rbac: 28 | create: true 29 | 30 | resources: 31 | # We usually recommend not to specify default resources and to leave this as a conscious 32 | # choice for the user. This also increases chances charts run on environments with little 33 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 34 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 35 | # limits: 36 | # cpu: 100m 37 | # memory: 128Mi 38 | requests: 39 | # cpu: 100m 40 | memory: 275Mi 41 | 42 | nodeSelector: {} 43 | 44 | tolerations: [] 45 | 46 | affinity: {} 47 | 48 | postgresql: 49 | enabled: true 50 | nameOverride: user-db 51 | fullnameOverride: user-db 52 | postgresqlPassword: example 53 | persistence: 54 | enabled: false 55 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: kafka 3 | repository: https://kubernetes-charts-incubator.storage.googleapis.com 4 | version: 0.13.3 5 | - name: prometheus 6 | repository: https://kubernetes-charts.storage.googleapis.com 7 | version: 8.3.0 8 | - name: grafana 9 | repository: https://kubernetes-charts.storage.googleapis.com 10 | version: 1.21.2 11 | digest: sha256:7dd5856caf2ab403ad9116504554e6c39df1178c1dbb51388a79e28723c410ce 12 | generated: "2020-02-16T23:09:37.870674396Z" 13 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: kafka 3 | version: 0.13.3 4 | repository: https://kubernetes-charts-incubator.storage.googleapis.com 5 | condition: kafka.enabled 6 | - name: prometheus 7 | version: 8.3.0 8 | repository: https://kubernetes-charts.storage.googleapis.com 9 | condition: prometheus.enabled 10 | - name: grafana 11 | version: 1.21.2 12 | repository: https://kubernetes-charts.storage.googleapis.com 13 | condition: grafana.enabled -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | Your Hyperskale Social Network example has been successfully deployed to Kubernetes. 2 | 3 | 1. Run kubectl port forward to access the app: 4 | 5 | $ kubectl -n {{ .Release.Namespace }} port-forward svc/edge-service 9000 6 | 7 | 2. Check that it is healthy 8 | 9 | $ curl localhost:9000/actuator/health 10 | {"status":"UP"} 11 | 12 | 3. Create some content 13 | $ bash deployment/sbin/generate-serial.sh 14 | 15 | OR 16 | 17 | $ bash deployment/sbin/generate-parallel.sh 18 | 19 | {{ if .Values.grafana.enabled }} 20 | Micrometer metrics for Spring Boot can be monitored and viewed in Grafana. 21 | 22 | 1. Run kubectl port forward to access the app: 23 | 24 | $ kubectl -n {{ .Release.Namespace }} port-forward svc/{{ .Release.Name }}-grafana 8080:80 25 | 26 | 2. Access Grafana via your web browser at `http://localhost:8080` 27 | 28 | username: `admin` 29 | password: `{{ .Values.grafana.adminPassword }}` 30 | 31 | 3. Browse to the `Micrometer JVM Statistics` dashboard and choose your `application` and `instance`. 32 | {{ end }} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "social-network.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "social-network.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "social-network.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/templates/grafana-dashboard-spring.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.grafana.enabled }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ include "social-network.fullname" . }}-grafana-dashboard-spring 6 | labels: 7 | app: {{ include "social-network.name" . }} 8 | chart: {{ include "social-network.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | data: 12 | {{- (.Files.Glob "dashboards/*").AsConfig | nindent 2 }} 13 | {{ end }} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/social-network/values.yaml: -------------------------------------------------------------------------------- 1 | nameOverride: social-network 2 | fullNameOverride: social-network 3 | 4 | kafka: 5 | enabled: true 6 | nameOverride: kafka 7 | fullnameOverride: kafka 8 | persistence: 9 | enabled: false 10 | resources: 11 | requests: 12 | memory: 325Mi 13 | 14 | 15 | friend-service: 16 | fullnameOverride: friend-service 17 | kafka: 18 | enabled: true 19 | eventhub: 20 | name: 21 | connection: "" 22 | 23 | recommendation-service: 24 | fullnameOverride: recommendation-service 25 | kafka: 26 | enabled: true 27 | eventhub: 28 | name: 29 | connection: "" 30 | 31 | user-service: 32 | fullnameOverride: user-service 33 | kafka: 34 | enabled: true 35 | eventhub: 36 | name: 37 | connection: "" 38 | edge-service: 39 | fullnameOverride: edge-service 40 | 41 | prometheus: 42 | enabled: true 43 | fullnameOverride: prometheus 44 | nameOverride: prometheus 45 | server: 46 | resources: 47 | requests: 48 | memory: 150Mi 49 | persistentVolume: 50 | enabled: false 51 | alertmanager: 52 | enabled: false 53 | pushgateway: 54 | enabled: false 55 | 56 | grafana: 57 | enabled: true 58 | adminPassword: password 59 | datasources: 60 | datasources.yaml: 61 | apiVersion: 1 62 | datasources: 63 | - name: Prometheus 64 | type: prometheus 65 | url: http://social-network-prometheus-server 66 | isDefault: true 67 | access: proxy 68 | - name: Users 69 | type: postgres 70 | url: user-db 71 | database: postgres 72 | user: postgres 73 | secureJsonData: 74 | password: "example" 75 | jsonData: 76 | sslmode: "disable" 77 | maxOpenConns: 0 78 | maxIdleConns: 2 79 | connMaxLifetime: 14400 80 | postgresVersion: 1000 81 | timescaledb: false 82 | - name: Friends 83 | type: postgres 84 | url: friend-db 85 | database: postgres 86 | user: postgres 87 | secureJsonData: 88 | password: "example" 89 | jsonData: 90 | sslmode: "disable" 91 | maxOpenConns: 0 92 | maxIdleConns: 2 93 | connMaxLifetime: 14400 94 | postgresVersion: 1000 95 | timescaledb: false 96 | dashboardsConfigMaps: 97 | spring: social-network-grafana-dashboard-spring 98 | dashboardProviders: 99 | dashboardproviders.yaml: 100 | apiVersion: 1 101 | providers: 102 | - name: 'spring' 103 | orgId: 1 104 | folder: '' 105 | type: file 106 | disableDeletion: false 107 | editable: true 108 | options: 109 | path: /var/lib/grafana/dashboards/spring 110 | grafana.ini: 111 | users: 112 | default_theme: "light" 113 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/user-service/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/user-service/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "0.0.2-SNAPSHOT" 3 | description: Helm chart for the user microservice in hyperskale/social-network 4 | name: user-service 5 | version: 0.2.0 6 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/user-service/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://kubernetes-charts.storage.googleapis.com/ 4 | version: 3.1.4 5 | digest: sha256:f33715012341b011cc243f722b688e091244b161a1e6c82f73572d12ff2f8b03 6 | generated: "2020-02-16T23:09:55.636759446Z" 7 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/user-service/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | version: 3.1.4 4 | repository: https://kubernetes-charts.storage.googleapis.com/ 5 | condition: postgresql.enabled 6 | tags: 7 | - friend-db -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/user-service/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if contains "NodePort" .Values.service.type }} 3 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "user-service.fullname" . }}) 4 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 5 | echo http://$NODE_IP:$NODE_PORT 6 | {{- else if contains "LoadBalancer" .Values.service.type }} 7 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 8 | You can watch the status of by running 'kubectl get svc -w {{ include "user-service.fullname" . }}' 9 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "user-service.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 10 | echo http://$SERVICE_IP:{{ .Values.service.port }} 11 | {{- else if contains "ClusterIP" .Values.service.type }} 12 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ include "user-service.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 13 | echo "Visit http://127.0.0.1:8080 to use your application" 14 | kubectl port-forward $POD_NAME 8080:80 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/user-service/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "user-service.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "user-service.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "user-service.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create the name of the service account to use 36 | */}} 37 | {{- define "user-service.serviceAccountName" -}} 38 | {{- if .Values.serviceAccount.create -}} 39 | {{ default (include "user-service.fullname" .) .Values.serviceAccount.name }} 40 | {{- else -}} 41 | {{ default "default" .Values.serviceAccount.name }} 42 | {{- end -}} 43 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/user-service/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ template "user-service.fullname" . }} 6 | labels: 7 | app: {{ template "user-service.name" . }} 8 | chart: {{ template "user-service.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | rules: 12 | - apiGroups: [""] 13 | resources: ["services", "pods", "endpoints", "configmaps"] 14 | verbs: ["get","list"] 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1beta1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: {{ template "user-service.fullname" . }} 20 | labels: 21 | app: {{ template "user-service.name" . }} 22 | chart: {{ template "user-service.chart" . }} 23 | release: {{ .Release.Name }} 24 | heritage: {{ .Release.Service }} 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: {{ template "user-service.fullname" . }} 29 | subjects: 30 | - name: {{ template "user-service.serviceAccountName" . }} 31 | namespace: {{ .Release.Namespace | quote }} 32 | kind: ServiceAccount 33 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/user-service/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "user-service.fullname" . }} 5 | labels: 6 | app: {{ include "user-service.name" . }} 7 | chart: {{ include "user-service.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: http 15 | protocol: TCP 16 | name: http 17 | selector: 18 | app: {{ include "user-service.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/user-service/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "user-service.serviceAccountName" . }} 6 | namespace: {{ .Release.Namespace | quote }} 7 | labels: 8 | app: {{ template "user-service.name" . }} 9 | chart: {{ template "user-service.chart" . }} 10 | release: {{ .Release.Name }} 11 | heritage: {{ .Release.Service }} 12 | {{- end -}} -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/helm/user-service/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for discovery-service. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | kafka: 7 | enabled: true 8 | eventhub: 9 | name: "" 10 | connection: "" 11 | image: 12 | repository: hyperskale/user-service 13 | tag: 0.0.2-SNAPSHOT 14 | pullPolicy: Always 15 | 16 | nameOverride: "" 17 | fullnameOverride: "" 18 | 19 | service: 20 | type: ClusterIP 21 | port: 8120 22 | 23 | serviceAccount: 24 | create: true 25 | # name: ~ 26 | 27 | rbac: 28 | create: true 29 | 30 | resources: 31 | # We usually recommend not to specify default resources and to leave this as a conscious 32 | # choice for the user. This also increases chances charts run on environments with little 33 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 34 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 35 | # limits: 36 | # cpu: 100m 37 | # memory: 128Mi 38 | requests: 39 | # cpu: 100m 40 | memory: 275Mi 41 | 42 | nodeSelector: {} 43 | 44 | tolerations: [] 45 | 46 | affinity: {} 47 | 48 | postgresql: 49 | enabled: true 50 | nameOverride: user-db 51 | fullnameOverride: user-db 52 | postgresqlPassword: example 53 | persistence: 54 | enabled: false 55 | -------------------------------------------------------------------------------- /Chapter09/social-network/deployment/sbin/names-15.txt: -------------------------------------------------------------------------------- 1 | Andrew Rutherford 2 | Andrew Parr 3 | Emily Morgan 4 | Kevin Bailey 5 | Bernadette Butler 6 | Michael White 7 | Gavin Fraser 8 | Bernadette Blake 9 | Gabrielle Carr 10 | Emily Ball 11 | Sophie James 12 | Jacob Thomson 13 | Zoe Ogden 14 | Hannah Walsh 15 | Rachel Russell -------------------------------------------------------------------------------- /Chapter09/social-network/discovery-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.1-jre-slim-sid 2 | 3 | CMD ["/usr/bin/java", "-jar", "/usr/share/myservice/myservice.jar"] 4 | 5 | # Add the service itself 6 | ARG JAR_FILE 7 | COPY target/${JAR_FILE} /usr/share/myservice/myservice.jar -------------------------------------------------------------------------------- /Chapter09/social-network/discovery-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | discovery-service 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | discovery-service 11 | 12 | 13 | io.example 14 | event-sourcing-microservices-example 15 | 0.0.1-SNAPSHOT 16 | ../ 17 | 18 | 19 | 20 | 21 | org.springframework.cloud 22 | spring-cloud-starter-netflix-eureka-server 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | test 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-maven-plugin 37 | 38 | 39 | com.spotify 40 | dockerfile-maven-plugin 41 | 1.4.9 42 | 43 | 44 | default 45 | 46 | build 47 | push 48 | 49 | 50 | 51 | 52 | ${docker.user}/${project.name} 53 | ${project.version} 54 | 55 | ${project.build.finalName}.jar 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-compiler-plugin 62 | 63 | 11 64 | 11 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Chapter09/social-network/discovery-service/src/main/java/io/example/DiscoveryServiceApplication.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaServer 9 | public class DiscoveryServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DiscoveryServiceApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter09/social-network/discovery-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: development 4 | server: 5 | port: 8761 6 | logging: 7 | level: 8 | root: INFO 9 | kafka.*: WARN 10 | org.springframework.*: INFO 11 | org.neo4j.*: INFO 12 | com.memorynotfound: DEBUG 13 | org.apache.kafka.*: ERROR 14 | --- 15 | spring: 16 | profiles: development 17 | cloud: 18 | kubernetes.discovery.enabled: false 19 | kubernetes.ribbon.enabled: false 20 | kubernetes.enabled: false 21 | eureka: 22 | instance: 23 | preferIpAddress: true 24 | --- 25 | spring: 26 | profiles: docker 27 | cloud: 28 | kubernetes.discovery.enabled: false 29 | kubernetes.ribbon.enabled: false 30 | kubernetes.enabled: false 31 | eureka: 32 | instance: 33 | preferIpAddress: true 34 | --- 35 | spring: 36 | profiles: test 37 | cloud: 38 | kubernetes.discovery.enabled: false 39 | kubernetes.ribbon.enabled: false 40 | kubernetes.enabled: false 41 | prometheus.enabled: false 42 | eureka: 43 | client: 44 | enabled: false -------------------------------------------------------------------------------- /Chapter09/social-network/discovery-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: default,development,test,docker 3 | application: 4 | name: discovery-service 5 | management: 6 | endpoints: 7 | web: 8 | exposure: 9 | include: "*" 10 | enabled-by-default: true 11 | metrics: 12 | tags: 13 | application: ${spring.application.name} 14 | environment: ${spring.profiles} 15 | export: 16 | prometheus: 17 | descriptions: true 18 | enabled: ${prometheus.enabled:true} # Whether exporting of metrics to Prometheus is enabled. 19 | step: 1m # Step size (i.e. reporting frequency) to use. 20 | pushgateway: 21 | base-url: ${prometheus.url:localhost:9091} # Base URL for the Pushgateway. 22 | enabled: ${prometheus.push.enabled:true} # Enable publishing via a Prometheus Pushgateway. 23 | job: ${spring.application.name} # Job identifier for this application instance. 24 | push-rate: 1m # Frequency with which to push metrics. 25 | shutdown-operation: push # Operation that should be performed on shutdown. 26 | 27 | --- -------------------------------------------------------------------------------- /Chapter09/social-network/discovery-service/src/test/java/io/example/DiscoveryServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.test.context.ActiveProfiles; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | import org.springframework.util.Assert; 8 | 9 | @RunWith(SpringRunner.class) 10 | @ActiveProfiles("test") 11 | public class DiscoveryServiceApplicationTests { 12 | 13 | @Test 14 | public void testsVoid() { 15 | Assert.state(true, "The tests for this service are turned off for this example."); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Chapter09/social-network/edge-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.1-jre-slim-sid 2 | 3 | CMD ["/usr/bin/java", "-jar", "/usr/share/myservice/myservice.jar"] 4 | 5 | # Add the service itself 6 | ARG JAR_FILE 7 | COPY target/${JAR_FILE} /usr/share/myservice/myservice.jar -------------------------------------------------------------------------------- /Chapter09/social-network/edge-service/src/main/java/io/example/EdgeServiceApplication.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.web.client.RestTemplate; 9 | import org.springframework.web.reactive.config.EnableWebFlux; 10 | 11 | @SpringBootApplication 12 | @EnableDiscoveryClient 13 | @EnableWebFlux 14 | public class EdgeServiceApplication { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(EdgeServiceApplication.class, args); 18 | } 19 | 20 | @Bean 21 | @LoadBalanced 22 | public RestTemplate restTemplate() { 23 | return new RestTemplate(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter09/social-network/edge-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: default,development,test,docker,kubernetes 3 | application: 4 | name: edge-service 5 | management: 6 | endpoints: 7 | web: 8 | exposure: 9 | include: "*" 10 | enabled-by-default: true 11 | metrics: 12 | tags: 13 | application: ${spring.application.name} 14 | environment: ${spring.profiles} 15 | export: 16 | prometheus: 17 | descriptions: true 18 | enabled: ${prometheus.enabled:true} # Whether exporting of metrics to Prometheus is enabled. 19 | step: 1m # Step size (i.e. reporting frequency) to use. 20 | pushgateway: 21 | base-url: ${prometheus.url:localhost:9091} # Base URL for the Pushgateway. 22 | enabled: ${prometheus.push.enabled:true} # Enable publishing via a Prometheus Pushgateway. 23 | job: ${spring.application.name} # Job identifier for this application instance. 24 | push-rate: 1m # Frequency with which to push metrics. 25 | shutdown-operation: push # Operation that should be performed on shutdown. 26 | --- -------------------------------------------------------------------------------- /Chapter09/social-network/edge-service/src/test/java/io/example/EdgeServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.test.context.ActiveProfiles; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | import org.springframework.util.Assert; 11 | 12 | @RunWith(SpringRunner.class) 13 | @SpringBootTest 14 | @ActiveProfiles("test") 15 | public class EdgeServiceApplicationTests { 16 | 17 | @Autowired 18 | private ApplicationContext applicationContext; 19 | 20 | @Test 21 | public void contextLoads() { 22 | Assert.notNull(applicationContext, "The application context should not be null"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.1-jre-slim-sid 2 | 3 | CMD ["/usr/bin/java", "-jar", "/usr/share/myservice/myservice.jar"] 4 | 5 | # Add the service itself 6 | ARG JAR_FILE 7 | COPY target/${JAR_FILE} /usr/share/myservice/myservice.jar -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/java/io/example/FriendServiceApplication.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | import org.springframework.boot.WebApplicationType; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.builder.SpringApplicationBuilder; 6 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 7 | import org.springframework.cloud.stream.annotation.EnableBinding; 8 | import org.springframework.cloud.stream.messaging.Source; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.web.reactive.function.client.WebClient; 12 | 13 | /** 14 | * The microservice that manages domain data for friend requests. 15 | * 16 | * @author Kenny Bastani 17 | */ 18 | @SpringBootApplication 19 | public class FriendServiceApplication { 20 | 21 | public static void main(String[] args) { 22 | new SpringApplicationBuilder(FriendServiceApplication.class).web(WebApplicationType.REACTIVE).run(args); 23 | } 24 | 25 | /** 26 | * Configures a {@link Source} channel binding for emitting domain events 27 | * to a Spring Cloud Stream adapter. 28 | */ 29 | @Configuration 30 | @EnableBinding(Source.class) 31 | class StreamConfig { 32 | } 33 | 34 | @Bean 35 | @LoadBalanced 36 | public WebClient.Builder userWebClient() { 37 | return WebClient.builder(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/java/io/example/domain/friend/DomainEvent.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | public abstract class DomainEvent { 4 | 5 | private T subject; 6 | 7 | public DomainEvent() { 8 | } 9 | 10 | public DomainEvent(T subject) { 11 | this.subject = subject; 12 | } 13 | 14 | public T getSubject() { 15 | return subject; 16 | } 17 | 18 | public void setSubject(T subject) { 19 | this.subject = subject; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/java/io/example/domain/friend/EventType.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | /** 4 | * The type of events that affect the state of a {@link Friend}. 5 | * 6 | * @author Kenny Bastani 7 | */ 8 | public enum EventType { 9 | FRIEND_ADDED, 10 | FRIEND_REMOVED 11 | } 12 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/java/io/example/domain/friend/Friend.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.relational.core.mapping.Column; 5 | import org.springframework.data.relational.core.mapping.Table; 6 | 7 | import java.sql.Timestamp; 8 | import java.util.Objects; 9 | 10 | /** 11 | * The domain entity representing a friend relationship between two users. 12 | * 13 | * @author Kenny Bastani 14 | */ 15 | @Table("friend") 16 | public class Friend { 17 | 18 | @Id 19 | private Long id; 20 | 21 | @Column(value = "user_id") 22 | private Long userId; 23 | 24 | @Column(value = "friend_id") 25 | private Long friendId; 26 | 27 | @Column(value = "created_at") 28 | private Timestamp createdAt; 29 | 30 | @Column(value = "updated_at") 31 | private Timestamp updatedAt; 32 | 33 | public Friend() { 34 | } 35 | 36 | public Friend(Long userId, Long friendId) { 37 | this.userId = userId; 38 | this.friendId = friendId; 39 | } 40 | 41 | public Friend(Long id, Long userId, Long friendId) { 42 | this.id = id; 43 | this.userId = userId; 44 | this.friendId = friendId; 45 | } 46 | 47 | public Long getId() { 48 | return id; 49 | } 50 | 51 | public void setId(Long id) { 52 | this.id = id; 53 | } 54 | 55 | public Long getUserId() { 56 | return userId; 57 | } 58 | 59 | public void setUserId(Long userId) { 60 | this.userId = userId; 61 | } 62 | 63 | public Long getFriendId() { 64 | return friendId; 65 | } 66 | 67 | public void setFriendId(Long friendId) { 68 | this.friendId = friendId; 69 | } 70 | 71 | public Timestamp getCreatedAt() { 72 | return createdAt; 73 | } 74 | 75 | public void setCreatedAt(Timestamp createdAt) { 76 | this.createdAt = createdAt; 77 | } 78 | 79 | public Timestamp getUpdatedAt() { 80 | return updatedAt; 81 | } 82 | 83 | public void setUpdatedAt(Timestamp updatedAt) { 84 | this.updatedAt = updatedAt; 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return "Friend{" + 90 | "id=" + id + 91 | ", userId=" + userId + 92 | ", friendId=" + friendId + 93 | ", createdAt=" + createdAt + 94 | ", updatedAt=" + updatedAt + 95 | '}'; 96 | } 97 | 98 | @Override 99 | public boolean equals(Object o) { 100 | if (this == o) return true; 101 | if (o == null || getClass() != o.getClass()) return false; 102 | Friend friend = (Friend) o; 103 | return Objects.equals(id, friend.id) && 104 | Objects.equals(userId, friend.userId) && 105 | Objects.equals(friendId, friend.friendId) && 106 | Objects.equals(createdAt, friend.createdAt) && 107 | Objects.equals(updatedAt, friend.updatedAt); 108 | } 109 | 110 | @Override 111 | public int hashCode() { 112 | 113 | return Objects.hash(id, userId, friendId, createdAt, updatedAt); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/java/io/example/domain/friend/FriendEvent.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | /** 4 | * An event that encapsulates a state transition for the {@link Friend} 5 | * domain object. 6 | * 7 | * @link Kenny Bastani 8 | */ 9 | public class FriendEvent extends DomainEvent { 10 | 11 | private Friend subject; 12 | private EventType eventType; 13 | private FriendMessage friendMessage; 14 | 15 | public FriendEvent(EventType friendRemoved) { 16 | } 17 | 18 | public FriendEvent(Friend subject, EventType eventType) { 19 | this.subject = subject; 20 | this.eventType = eventType; 21 | } 22 | 23 | public FriendEvent(Friend subject, EventType eventType, FriendMessage friendMessage) { 24 | this.subject = subject; 25 | this.eventType = eventType; 26 | this.friendMessage = friendMessage; 27 | } 28 | 29 | public FriendEvent(Friend subject, Friend subject1, EventType eventType, FriendMessage friendMessage) { 30 | super(subject); 31 | this.subject = subject1; 32 | this.eventType = eventType; 33 | this.friendMessage = friendMessage; 34 | } 35 | 36 | public FriendMessage getFriendMessage() { 37 | return friendMessage; 38 | } 39 | 40 | public void setFriendMessage(FriendMessage friendMessage) { 41 | this.friendMessage = friendMessage; 42 | } 43 | 44 | public Friend getSubject() { 45 | return subject; 46 | } 47 | 48 | public void setSubject(Friend subject) { 49 | this.subject = subject; 50 | } 51 | 52 | public EventType getEventType() { 53 | return eventType; 54 | } 55 | 56 | public void setEventType(EventType eventType) { 57 | this.eventType = eventType; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/java/io/example/domain/friend/FriendMessage.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | import java.sql.Timestamp; 4 | 5 | public class FriendMessage extends DomainEvent { 6 | 7 | private Long userId; 8 | private Long friendId; 9 | private Timestamp createdAt; 10 | private Timestamp updatedAt; 11 | 12 | public FriendMessage() { 13 | } 14 | 15 | public FriendMessage(Long userId, Long friendId) { 16 | this.userId = userId; 17 | this.friendId = friendId; 18 | } 19 | 20 | public FriendMessage(Long userId, Long friendId, Timestamp createdAt, Timestamp updatedAt) { 21 | this.userId = userId; 22 | this.friendId = friendId; 23 | this.createdAt = createdAt; 24 | this.updatedAt = updatedAt; 25 | } 26 | 27 | public Long getUserId() { 28 | return userId; 29 | } 30 | 31 | public void setUserId(Long userId) { 32 | this.userId = userId; 33 | } 34 | 35 | public Long getFriendId() { 36 | return friendId; 37 | } 38 | 39 | public void setFriendId(Long friendId) { 40 | this.friendId = friendId; 41 | } 42 | 43 | public Timestamp getCreatedAt() { 44 | return createdAt; 45 | } 46 | 47 | public void setCreatedAt(Timestamp createdAt) { 48 | this.createdAt = createdAt; 49 | } 50 | 51 | public Timestamp getUpdatedAt() { 52 | return updatedAt; 53 | } 54 | 55 | public void setUpdatedAt(Timestamp updatedAt) { 56 | this.updatedAt = updatedAt; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "FriendMessage{" + 62 | "userId=" + userId + 63 | ", friendId=" + friendId + 64 | ", createdAt=" + createdAt + 65 | ", updatedAt=" + updatedAt + 66 | "} " + super.toString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/java/io/example/domain/friend/FriendRepository.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | import org.springframework.data.r2dbc.repository.query.Query; 4 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | 8 | /** 9 | * A repository for managing {@link Friend} entities. 10 | * 11 | * @author Kenny Bastani 12 | */ 13 | public interface FriendRepository extends ReactiveCrudRepository { 14 | 15 | @Query("SELECT f.id, f.user_id, f.friend_id FROM Friend f WHERE user_id = $1 AND friend_id = $2") 16 | Mono getFriend(Long userId, Long friendId); 17 | 18 | @Query("SELECT f.id, f.user_id, f.friend_id FROM Friend f WHERE f.user_id = $1") 19 | Flux getFriends(Long userId); 20 | } 21 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/java/io/example/domain/user/User.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.user; 2 | 3 | import org.springframework.integration.support.json.Jackson2JsonObjectMapper; 4 | 5 | import java.util.Date; 6 | import java.util.Random; 7 | 8 | /** 9 | * A projection of the {@link User} domain object that is owned by the user service. 10 | * 11 | * @author Kenny Bastani 12 | */ 13 | public class User { 14 | 15 | private Long id; 16 | private Long userId; 17 | private String firstName; 18 | private String lastName; 19 | private Date createdAt; 20 | private Date lastModified; 21 | 22 | public User() { 23 | userId = Math.abs(new Random().nextLong()); 24 | } 25 | 26 | public User(String firstName, String lastName) { 27 | this(); 28 | this.firstName = firstName; 29 | this.lastName = lastName; 30 | } 31 | 32 | public User(Long id, String firstName, String lastName) { 33 | this.userId = id; 34 | this.firstName = firstName; 35 | this.lastName = lastName; 36 | } 37 | 38 | public User(Long userId, String firstName, String lastName, Date createdAt, Date lastModified) { 39 | this.userId = userId; 40 | this.firstName = firstName; 41 | this.lastName = lastName; 42 | this.createdAt = createdAt; 43 | this.lastModified = lastModified; 44 | } 45 | 46 | public Long getId() { 47 | return userId; 48 | } 49 | 50 | public void setId(Long id) { 51 | this.userId = id; 52 | } 53 | 54 | public String getFirstName() { 55 | return firstName; 56 | } 57 | 58 | public void setFirstName(String firstName) { 59 | this.firstName = firstName; 60 | } 61 | 62 | public String getLastName() { 63 | return lastName; 64 | } 65 | 66 | public void setLastName(String lastName) { 67 | this.lastName = lastName; 68 | } 69 | 70 | public Date getCreatedAt() { 71 | return createdAt; 72 | } 73 | 74 | public void setCreatedAt(Date createdAt) { 75 | this.createdAt = createdAt; 76 | } 77 | 78 | public Date getLastModified() { 79 | return lastModified; 80 | } 81 | 82 | public void setLastModified(Date lastModified) { 83 | this.lastModified = lastModified; 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | try { 89 | return new Jackson2JsonObjectMapper().toJson(this); 90 | } catch (Exception e) { 91 | e.printStackTrace(); 92 | } 93 | 94 | return "User{" + 95 | "id=" + id + 96 | ", userId=" + userId + 97 | ", firstName='" + firstName + '\'' + 98 | ", lastName='" + lastName + '\'' + 99 | ", createdAt=" + createdAt + 100 | ", updatedAt=" + lastModified + 101 | '}'; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/java/io/example/domain/user/UserClient.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.user; 2 | 3 | import org.springframework.http.MediaType; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import reactor.core.publisher.Mono; 7 | 8 | @Service 9 | public class UserClient { 10 | 11 | private final WebClient.Builder userWebClient; 12 | 13 | public UserClient(WebClient.Builder userWebClient) { 14 | this.userWebClient = userWebClient; 15 | } 16 | 17 | public Mono getUser(Long userId) { 18 | 19 | Mono user; 20 | 21 | try { 22 | user = userWebClient.baseUrl("http://user-service/").build().get() 23 | .uri("v1/users/{userId}", userId) 24 | .accept(MediaType.APPLICATION_JSON) 25 | .retrieve() 26 | .bodyToMono(User.class) 27 | .single() 28 | .log(); 29 | } catch (Exception ex) { 30 | throw new RuntimeException("Could not retrieve user", ex); 31 | } 32 | 33 | return user; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: default,development,test,docker,kubernetes 3 | application: 4 | name: friend-service 5 | management: 6 | endpoints: 7 | web: 8 | exposure: 9 | include: "*" 10 | enabled-by-default: true 11 | metrics: 12 | tags: 13 | application: ${spring.application.name} 14 | environment: ${spring.profiles} 15 | export: 16 | prometheus: 17 | descriptions: true 18 | enabled: ${prometheus.enabled:true} # Whether exporting of metrics to Prometheus is enabled. 19 | step: 1m # Step size (i.e. reporting frequency) to use. 20 | pushgateway: 21 | base-url: ${prometheus.url:localhost:9091} # Base URL for the Pushgateway. 22 | enabled: ${prometheus.push.enabled:true} # Enable publishing via a Prometheus Pushgateway. 23 | job: ${spring.application.name} # Job identifier for this application instance. 24 | push-rate: 1m # Frequency with which to push metrics. 25 | shutdown-operation: push # Operation that should be performed on shutdown. 26 | --- 27 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/main/resources/db/changelog/db.changelog-master.yaml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: 1 4 | author: friend-service 5 | context: friend 6 | changes: 7 | - createTable: 8 | tableName: friend 9 | columns: 10 | - column: 11 | name: id 12 | type: int 13 | autoIncrement: true 14 | constraints: 15 | primaryKey: true 16 | nullable: false 17 | - column: 18 | name: user_id 19 | type: int 20 | constraints: 21 | nullable: false 22 | - column: 23 | name: friend_id 24 | type: int 25 | constraints: 26 | nullable: false 27 | - column: 28 | name: created_at 29 | type: java.sql.Types.TIMESTAMP 30 | defaultValueComputed: NOW() 31 | constraints: 32 | nullable: false 33 | - column: 34 | name: updated_at 35 | type: java.sql.Types.TIMESTAMP 36 | defaultValueComputed: NOW() 37 | valueComputed: NOW() 38 | constraints: 39 | nullable: false -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/test/java/io/example/AbstractIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.junit.ClassRule; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.boot.test.util.TestPropertyValues; 8 | import org.springframework.context.ApplicationContextInitializer; 9 | import org.springframework.context.ConfigurableApplicationContext; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | import org.testcontainers.containers.GenericContainer; 14 | import org.testcontainers.containers.KafkaContainer; 15 | 16 | @RunWith(SpringJUnit4ClassRunner.class) 17 | @SpringBootTest(classes = FriendServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 18 | @ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class) 19 | @ActiveProfiles("test") 20 | public abstract class AbstractIntegrationTest { 21 | 22 | @ClassRule 23 | public static KafkaContainer kafka = new KafkaContainer(); 24 | 25 | @ClassRule 26 | public static GenericContainer postgres = new GenericContainer("postgres:9.6.8") 27 | .withExposedPorts(5432) 28 | .withEnv("POSTGRES_PASSWORD", "password") 29 | .withEnv("POSTGRES_USER", "postgres"); 30 | 31 | 32 | static { 33 | postgres.start(); 34 | } 35 | 36 | public static class Initializer implements ApplicationContextInitializer { 37 | @Override 38 | public void initialize(@NotNull ConfigurableApplicationContext configurableApplicationContext) { 39 | String jdbcUrl = String.format("jdbc:postgresql://%s:%d/%s", postgres.getContainerIpAddress(), 40 | postgres.getMappedPort(5432), "postgres"); 41 | TestPropertyValues values = TestPropertyValues.of( 42 | "postgres.host=" + postgres.getContainerIpAddress(), 43 | "postgres.port=" + postgres.getMappedPort(5432), 44 | "postgres.url=" + jdbcUrl, 45 | "spring.datasource.url=" + jdbcUrl, 46 | "spring.cloud.stream.kafka.binder.brokers=" + kafka.getBootstrapServers(), 47 | "eureka.client.enabled=false"); 48 | values.applyTo(configurableApplicationContext); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Chapter09/social-network/friend-service/src/test/java/io/example/AbstractUnitTest.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | import io.example.config.DataSourceConfiguration; 4 | import io.example.domain.friend.FriendService; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.junit.ClassRule; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 9 | import org.springframework.boot.test.util.TestPropertyValues; 10 | import org.springframework.context.ApplicationContextInitializer; 11 | import org.springframework.context.ConfigurableApplicationContext; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.test.context.ContextConfiguration; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | import org.testcontainers.containers.GenericContainer; 16 | 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | @ContextConfiguration(initializers = AbstractUnitTest.Initializer.class, classes = { 19 | DataSourceConfiguration.class, 20 | DataSourceAutoConfiguration.class, 21 | FriendService.class 22 | }) 23 | @ActiveProfiles("test") 24 | public abstract class AbstractUnitTest { 25 | 26 | @ClassRule 27 | public static GenericContainer postgres = new GenericContainer("postgres:9.6.8") 28 | .withExposedPorts(5432) 29 | .withEnv("POSTGRES_PASSWORD", "password") 30 | .withEnv("POSTGRES_USER", "postgres"); 31 | 32 | 33 | static { 34 | postgres.start(); 35 | } 36 | 37 | public static class Initializer implements ApplicationContextInitializer { 38 | @Override 39 | public void initialize(@NotNull ConfigurableApplicationContext configurableApplicationContext) { 40 | String jdbcUrl = String.format("jdbc:postgresql://%s:%d/%s", postgres.getContainerIpAddress(), 41 | postgres.getMappedPort(5432), "postgres"); 42 | 43 | TestPropertyValues values = TestPropertyValues.of( 44 | "postgres.host=" + postgres.getContainerIpAddress(), 45 | "postgres.port=" + postgres.getMappedPort(5432), 46 | "postgres.url=" + jdbcUrl, 47 | "postgres.database-name=postgres", 48 | "spring.application.name=friend-service", 49 | "spring.datasource.data-username=postgres", 50 | "spring.datasource.data-password=password", 51 | "spring.datasource.url=" + jdbcUrl, 52 | "eureka.client.enabled=false"); 53 | 54 | values.applyTo(configurableApplicationContext); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Chapter09/social-network/neo4j-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: neo4j-service 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | statefulset.kubernetes.io/pod-name: social-network-neo4j-core-0 9 | ports: 10 | - protocol: TCP 11 | name: internet 12 | port: 7474 13 | targetPort: 7474 14 | - protocol: TCP 15 | port: 7687 16 | name: bolt 17 | targetPort: 7687 18 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.1-jre-slim-sid 2 | 3 | CMD ["/usr/bin/java", "-jar", "/usr/share/myservice/myservice.jar"] 4 | 5 | # Add Maven dependencies (not shaded into the artifact; Docker-cached) 6 | ARG JAR_FILE 7 | COPY target/${JAR_FILE} /usr/share/myservice/myservice.jar -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/RecommendationService.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.domain.EntityScan; 6 | import org.springframework.boot.builder.SpringApplicationBuilder; 7 | import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; 8 | 9 | /** 10 | * This event stream processor monitors a stream of events from multiple microservices and builds an eventually 11 | * consistent view of all domain data as a graph in Neo4j. 12 | * 13 | * @author Kenny Bastani 14 | */ 15 | @SpringBootApplication 16 | @EnableNeo4jRepositories(value = {"io.example.domain.user", "io.example.domain.friend"}) 17 | @EntityScan({"io.example.domain.friend.entity", "io.example.domain.user.entity"}) 18 | public class RecommendationService { 19 | 20 | public static void main(String[] args) { 21 | new SpringApplicationBuilder(RecommendationService.class).run(args); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/friend/FriendEvent.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | import io.example.domain.friend.entity.Friend; 4 | 5 | /** 6 | * An event that encapsulates a state transition for the {@link Friend} 7 | * domain object. 8 | * 9 | * @link Kenny Bastani 10 | */ 11 | public class FriendEvent { 12 | 13 | private FriendMessage subject; 14 | private FriendEventType eventType; 15 | 16 | public FriendEvent() { 17 | } 18 | 19 | public FriendEvent(FriendMessage subject, FriendEventType eventType) { 20 | this.subject = subject; 21 | this.eventType = eventType; 22 | } 23 | 24 | public FriendMessage getSubject() { 25 | return subject; 26 | } 27 | 28 | public void setSubject(FriendMessage subject) { 29 | this.subject = subject; 30 | } 31 | 32 | public FriendEventType getEventType() { 33 | return eventType; 34 | } 35 | 36 | public void setEventType(FriendEventType eventType) { 37 | this.eventType = eventType; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "FriendEvent{" + 43 | "subject=" + subject + 44 | ", eventType=" + eventType + 45 | '}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/friend/FriendEventType.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | import io.example.domain.friend.entity.Friend; 4 | 5 | /** 6 | * The type of events that affect the state of a {@link Friend}. 7 | * 8 | * @author Kenny Bastani 9 | */ 10 | public enum FriendEventType { 11 | FRIEND_ADDED, 12 | FRIEND_REMOVED 13 | } 14 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/friend/FriendMessage.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | import java.sql.Timestamp; 4 | 5 | public class FriendMessage { 6 | 7 | private Long userId; 8 | private Long friendId; 9 | private Timestamp createdAt; 10 | private Timestamp updatedAt; 11 | 12 | public FriendMessage() { 13 | } 14 | 15 | public FriendMessage(Long userId, Long friendId) { 16 | this.userId = userId; 17 | this.friendId = friendId; 18 | } 19 | 20 | public FriendMessage(Long userId, Long friendId, Timestamp createdAt, Timestamp updatedAt) { 21 | this.userId = userId; 22 | this.friendId = friendId; 23 | this.createdAt = createdAt; 24 | this.updatedAt = updatedAt; 25 | } 26 | 27 | public Long getUserId() { 28 | return userId; 29 | } 30 | 31 | public void setUserId(Long userId) { 32 | this.userId = userId; 33 | } 34 | 35 | public Long getFriendId() { 36 | return friendId; 37 | } 38 | 39 | public void setFriendId(Long friendId) { 40 | this.friendId = friendId; 41 | } 42 | 43 | public Timestamp getCreatedAt() { 44 | return createdAt; 45 | } 46 | 47 | public void setCreatedAt(Timestamp createdAt) { 48 | this.createdAt = createdAt; 49 | } 50 | 51 | public Timestamp getUpdatedAt() { 52 | return updatedAt; 53 | } 54 | 55 | public void setUpdatedAt(Timestamp updatedAt) { 56 | this.updatedAt = updatedAt; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "FriendMessage{" + 62 | "userId=" + userId + 63 | ", friendId=" + friendId + 64 | ", createdAt=" + createdAt + 65 | ", updatedAt=" + updatedAt + 66 | '}'; 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/friend/FriendProcessor.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | import io.example.domain.friend.entity.Friend; 4 | import io.example.domain.user.UserRepository; 5 | import io.example.domain.user.entity.User; 6 | import org.springframework.cloud.stream.annotation.EnableBinding; 7 | import org.springframework.cloud.stream.annotation.StreamListener; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.messaging.Message; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.util.Arrays; 13 | import java.util.logging.Logger; 14 | 15 | /** 16 | * Message stream listener for {@link Friend} events. Maps types of events 17 | * to a graph operation that replicates a connected view of domain data 18 | * across microservices. 19 | * 20 | * @author Kenny Bastani 21 | */ 22 | @Configuration 23 | @EnableBinding(FriendSink.class) 24 | @Transactional 25 | public class FriendProcessor { 26 | 27 | private final Logger log = Logger.getLogger(FriendProcessor.class.getName()); 28 | private final FriendRepository friendRepository; 29 | private final UserRepository userRepository; 30 | 31 | public FriendProcessor(FriendRepository friendRepository, UserRepository userRepository) { 32 | this.friendRepository = friendRepository; 33 | this.userRepository = userRepository; 34 | } 35 | 36 | @StreamListener(value = FriendSink.INPUT) 37 | public void apply(Message friendEvent) { 38 | 39 | log.info("Event received: " + friendEvent.toString()); 40 | 41 | User user = userRepository.findUserByUserId(friendEvent.getPayload().getSubject().getUserId()); 42 | User friend = userRepository.findUserByUserId(friendEvent.getPayload().getSubject().getFriendId()); 43 | 44 | if (user == null || friend == null) { 45 | throw new RuntimeException("Invalid user identifier for " + friendEvent.getPayload().getEventType() + 46 | " operation on one or more users: " + Arrays.asList(user, friend).toString()); 47 | } 48 | 49 | switch (friendEvent.getPayload().getEventType()) { 50 | case FRIEND_ADDED: 51 | friendRepository.addFriend(user.getId(), friend.getId(), 52 | friendEvent.getPayload().getSubject().getCreatedAt().getTime(), 53 | friendEvent.getPayload().getSubject().getUpdatedAt().getTime()); 54 | break; 55 | case FRIEND_REMOVED: 56 | friendRepository.removeFriend(user.getId(), friend.getId()); 57 | break; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/friend/FriendRepository.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | import io.example.domain.friend.entity.Friend; 4 | import io.example.domain.friend.entity.RankedUser; 5 | import io.example.domain.user.entity.User; 6 | import org.springframework.data.neo4j.annotation.Query; 7 | import org.springframework.data.neo4j.repository.Neo4jRepository; 8 | import org.springframework.data.util.Streamable; 9 | import org.springframework.stereotype.Repository; 10 | 11 | /** 12 | * Repository for managing {@link User} data and friend connections. 13 | * 14 | * @author Kenny Bastani 15 | */ 16 | @Repository 17 | public interface FriendRepository extends Neo4jRepository { 18 | 19 | @Query("MATCH (userA:User)-[r:FRIEND]->(userB:User)" + 20 | "WHERE userA.userId={0} AND userB.userId={1} " + 21 | "DELETE r") 22 | void removeFriend(Long fromId, Long toId); 23 | 24 | @Query("MATCH (userA:User), (userB:User)" + 25 | "WHERE userA.userId={0} AND userB.userId={1} " + 26 | "CREATE (userA)-[:FRIEND { createdAt: {2}, lastUpdated: {3} }]->(userB)") 27 | void addFriend(Long fromId, Long toId, Long createdAt, Long lastUpdated); 28 | 29 | @Query("MATCH (userA:User), (userB:User)\n" + 30 | "WHERE userA.userId={0} AND userB.userId={1}\n" + 31 | "MATCH (userA)-[:FRIEND]-(fof:User)-[:FRIEND]-(userB)\n" + 32 | "RETURN DISTINCT fof") 33 | Streamable mutualFriends(Long fromId, Long toId); 34 | 35 | @Query("MATCH (me:User {userId: {0}})-[:FRIEND]-(friends),\n" + 36 | "\t(nonFriend:User)-[:FRIEND]-(friends)\n" + 37 | "WHERE NOT (me)-[:FRIEND]-(nonFriend)\n" + 38 | "WITH nonFriend, count(nonFriend) as mutualFriends\n" + 39 | "RETURN nonFriend as User, mutualFriends as Weight\n" + 40 | "ORDER BY Weight DESC") 41 | Streamable recommendedFriends(Long userId); 42 | } 43 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/friend/FriendSink.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend; 2 | 3 | import io.example.domain.friend.entity.Friend; 4 | import org.springframework.cloud.stream.annotation.Input; 5 | import org.springframework.cloud.stream.messaging.Sink; 6 | import org.springframework.messaging.SubscribableChannel; 7 | 8 | /** 9 | * Custom Spring Cloud Stream {@link Sink} binding for processing 10 | * events from the {@link Friend} channel. 11 | * 12 | * @author Kenny Bastani 13 | */ 14 | public interface FriendSink { 15 | 16 | String INPUT = "friend"; 17 | 18 | @Input(FriendSink.INPUT) 19 | SubscribableChannel friend(); 20 | } 21 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/friend/entity/Friend.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend.entity; 2 | 3 | import io.example.domain.user.entity.User; 4 | import org.neo4j.ogm.annotation.*; 5 | import org.neo4j.ogm.annotation.typeconversion.DateLong; 6 | 7 | import java.sql.Timestamp; 8 | import java.util.Date; 9 | import java.util.Objects; 10 | 11 | /** 12 | * A projection of the {@link Friend} domain object that is owned by the 13 | * friend service. 14 | * 15 | * @author Kenny Bastani 16 | */ 17 | @RelationshipEntity("FRIEND") 18 | public class Friend { 19 | @Id 20 | @GeneratedValue 21 | private Long id; 22 | 23 | @StartNode 24 | private User user; 25 | 26 | @EndNode 27 | private User friend; 28 | 29 | @DateLong 30 | private Date createdAt; 31 | 32 | @DateLong 33 | private Date lastModified; 34 | 35 | public Friend() { 36 | } 37 | 38 | public Friend(User user, User friend) { 39 | this.user = user; 40 | this.friend = friend; 41 | } 42 | 43 | public Long getId() { 44 | return id; 45 | } 46 | 47 | public void setId(Long id) { 48 | this.id = id; 49 | } 50 | 51 | public User getUser() { 52 | return user; 53 | } 54 | 55 | public void setUser(User user) { 56 | this.user = user; 57 | } 58 | 59 | public User getFriend() { 60 | return friend; 61 | } 62 | 63 | public void setFriend(User friend) { 64 | this.friend = friend; 65 | } 66 | 67 | public Date getCreatedAt() { 68 | return createdAt; 69 | } 70 | 71 | public void setCreatedAt(Timestamp createdAt) { 72 | this.createdAt = createdAt; 73 | } 74 | 75 | public Date getLastModified() { 76 | return lastModified; 77 | } 78 | 79 | public void setLastModified(Timestamp lastModified) { 80 | this.lastModified = lastModified; 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return "Friend{" + 86 | "id=" + id + 87 | ", user=" + user + 88 | ", friend=" + friend + 89 | ", createdAt=" + createdAt + 90 | ", lastModified=" + lastModified + 91 | '}'; 92 | } 93 | 94 | @Override 95 | public boolean equals(Object o) { 96 | if (this == o) return true; 97 | if (o == null || getClass() != o.getClass()) return false; 98 | Friend friend1 = (Friend) o; 99 | return Objects.equals(id, friend1.id) && 100 | Objects.equals(user, friend1.user) && 101 | Objects.equals(friend, friend1.friend); 102 | } 103 | 104 | @Override 105 | public int hashCode() { 106 | 107 | return Objects.hash(id, user, friend); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/friend/entity/RankedUser.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.friend.entity; 2 | 3 | import io.example.domain.user.entity.User; 4 | import org.springframework.data.neo4j.annotation.QueryResult; 5 | 6 | import java.util.Objects; 7 | 8 | /** 9 | * Get a list of {@link User} ranked by a weight. This model is used to get a ranked number of my friend's 10 | * mutual friends who I am not yet friends with. This is a typical recommendation query you would find on 11 | * most social networks. 12 | * 13 | * @author Kenny Bastani 14 | */ 15 | @QueryResult 16 | public class RankedUser { 17 | 18 | private User user; 19 | private Integer weight; 20 | 21 | public RankedUser() { 22 | } 23 | 24 | public RankedUser(User user, Integer weight) { 25 | this.user = user; 26 | this.weight = weight; 27 | } 28 | 29 | public User getUser() { 30 | return user; 31 | } 32 | 33 | public void setUser(User user) { 34 | this.user = user; 35 | } 36 | 37 | public Integer getWeight() { 38 | return weight; 39 | } 40 | 41 | public void setWeight(Integer weight) { 42 | this.weight = weight; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "RankedUser{" + 48 | "user=" + user + 49 | ", weight=" + weight + 50 | '}'; 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (o == null || getClass() != o.getClass()) return false; 57 | RankedUser that = (RankedUser) o; 58 | return Objects.equals(user, that.user) && 59 | Objects.equals(weight, that.weight); 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | 65 | return Objects.hash(user, weight); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/user/UserController.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.user; 2 | 3 | import io.example.domain.friend.FriendRepository; 4 | import io.example.domain.friend.entity.RankedUser; 5 | import io.example.domain.user.entity.User; 6 | import org.springframework.web.bind.annotation.*; 7 | import reactor.core.publisher.Flux; 8 | 9 | @RestController 10 | @RequestMapping("/v1") 11 | public class UserController { 12 | 13 | private final FriendRepository friendRepository; 14 | 15 | public UserController(FriendRepository friendRepository) { 16 | this.friendRepository = friendRepository; 17 | } 18 | 19 | @GetMapping(path = "/users/{userId}/commands/findMutualFriends") 20 | public Flux getMutualFriends(@PathVariable Long userId, @RequestParam Long friendId) { 21 | return Flux.fromIterable(friendRepository.mutualFriends(userId, friendId)); 22 | } 23 | 24 | @GetMapping(path = "/users/{userId}/commands/recommendFriends") 25 | public Flux recommendFriends(@PathVariable Long userId) { 26 | return Flux.fromIterable(friendRepository.recommendedFriends(userId)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/user/UserEvent.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.user; 2 | 3 | import io.example.domain.user.entity.User; 4 | 5 | /** 6 | * An event that encapsulates a state transition for the {@link User} 7 | * domain object. 8 | * 9 | * @link Kenny Bastani 10 | */ 11 | public class UserEvent { 12 | 13 | private User subject; 14 | private UserEventType eventType; 15 | 16 | public UserEvent() { 17 | } 18 | 19 | public UserEvent(User subject, UserEventType eventType) { 20 | this.subject = subject; 21 | this.eventType = eventType; 22 | } 23 | 24 | public User getSubject() { 25 | return subject; 26 | } 27 | 28 | public void setSubject(User subject) { 29 | this.subject = subject; 30 | } 31 | 32 | public UserEventType getEventType() { 33 | return eventType; 34 | } 35 | 36 | public void setEventType(UserEventType eventType) { 37 | this.eventType = eventType; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "UserEvent{" + 43 | "subject=" + subject + 44 | ", eventType=" + eventType + 45 | '}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/user/UserEventType.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.user; 2 | 3 | import io.example.domain.user.entity.User; 4 | 5 | /** 6 | * The type of events that affect the state of a {@link User}. 7 | * 8 | * @author Kenny Bastani 9 | */ 10 | public enum UserEventType { 11 | USER_CREATED, 12 | USER_UPDATED 13 | } 14 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/user/UserProcessor.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.user; 2 | 3 | import io.example.domain.user.entity.User; 4 | import org.springframework.cloud.stream.annotation.EnableBinding; 5 | import org.springframework.cloud.stream.annotation.StreamListener; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.messaging.Message; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import java.util.logging.Logger; 11 | 12 | /** 13 | * Message stream listener for {@link User} events. Maps types of events 14 | * to a graph operation that replicates a connected view of domain data 15 | * across microservices. 16 | * 17 | * @author Kenny Bastani 18 | */ 19 | @Configuration 20 | @EnableBinding(UserSink.class) 21 | public class UserProcessor { 22 | 23 | private final Logger log = Logger.getLogger(UserProcessor.class.getName()); 24 | private final UserRepository userRepository; 25 | 26 | public UserProcessor(UserRepository userRepository) { 27 | this.userRepository = userRepository; 28 | } 29 | 30 | @StreamListener(value = UserSink.INPUT) 31 | @Transactional 32 | public void apply(Message userEvent) { 33 | 34 | log.info("Event received: " + userEvent.getPayload().toString()); 35 | 36 | switch (userEvent.getPayload().getEventType()) { 37 | case USER_CREATED: 38 | User newUser = userRepository.save(userEvent.getPayload().getSubject()); 39 | log.info(String.format("Created user: %s", newUser)); 40 | break; 41 | case USER_UPDATED: 42 | User updateUser = userEvent.getPayload().getSubject(); 43 | User findUser = userRepository.findUserByUserId(updateUser.getId()); 44 | if(findUser != null) { 45 | findUser.setCreatedAt(updateUser.getCreatedAt()); 46 | findUser.setLastModified(updateUser.getLastModified()); 47 | findUser.setFirstName(updateUser.getFirstName()); 48 | findUser.setLastName(updateUser.getLastName()); 49 | findUser = userRepository.save(findUser); 50 | log.info(String.format("Updated user: %s", findUser.toString())); 51 | } 52 | break; 53 | default: 54 | break; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.user; 2 | 3 | import io.example.domain.user.entity.User; 4 | import org.springframework.data.neo4j.repository.Neo4jRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | /** 8 | * Repository for managing {@link User} data and friend connections. 9 | * 10 | * @author Kenny Bastani 11 | */ 12 | @Repository 13 | public interface UserRepository extends Neo4jRepository { 14 | 15 | User findUserByUserId(Long userId); 16 | } 17 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/java/io/example/domain/user/UserSink.java: -------------------------------------------------------------------------------- 1 | package io.example.domain.user; 2 | 3 | import io.example.domain.user.entity.User; 4 | import org.springframework.cloud.stream.annotation.Input; 5 | import org.springframework.cloud.stream.messaging.Sink; 6 | import org.springframework.messaging.SubscribableChannel; 7 | 8 | /** 9 | * Custom Spring Cloud Stream {@link Sink} binding for processing 10 | * events from the {@link User} channel. 11 | * 12 | * @author Kenny Bastani 13 | */ 14 | public interface UserSink { 15 | String INPUT = "user"; 16 | 17 | @Input(UserSink.INPUT) 18 | SubscribableChannel user(); 19 | } 20 | -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | stream: 4 | bindings: 5 | user: 6 | binder: kafka 7 | destination: user 8 | group: user-group 9 | contentType: 'application/json' 10 | consumer: 11 | concurrency: 1 12 | friend: 13 | binder: kafka 14 | destination: friend 15 | group: friend-group 16 | contentType: 'application/json' 17 | consumer: 18 | concurrency: 1 19 | profiles: 20 | active: development 21 | server: 22 | port: ${PORT:${SERVER_PORT:8110}} 23 | logging: 24 | level: 25 | root: INFO 26 | kafka.*: WARN 27 | org.springframework.*: INFO 28 | org.apache.kafka.*: WARN 29 | --- 30 | spring: 31 | profiles: development 32 | data: 33 | neo4j: 34 | uri: "bolt://localhost:7687" 35 | cloud: 36 | stream: 37 | kafka: 38 | binder: 39 | brokers: "localhost:9092" 40 | kubernetes.discovery.enabled: false 41 | kubernetes.ribbon.enabled: false 42 | kubernetes.enabled: false 43 | eureka: 44 | instance: 45 | prefer-ip-address: true 46 | client: 47 | registerWithEureka: true 48 | fetchRegistry: true 49 | serviceUrl: 50 | defaultZone: http://localhost:8761/eureka/ 51 | --- 52 | spring: 53 | profiles: docker 54 | data: 55 | neo4j: 56 | uri: "bolt://neo4j:7687" 57 | cloud: 58 | stream: 59 | kafka: 60 | binder: 61 | brokers: "kafka:29092" 62 | kubernetes.discovery.enabled: false 63 | kubernetes.ribbon.enabled: false 64 | kubernetes.enabled: false 65 | eureka: 66 | instance: 67 | prefer-ip-address: true 68 | client: 69 | registerWithEureka: true 70 | fetchRegistry: true 71 | serviceUrl: 72 | defaultZone: http://discovery-service:8761/eureka/ 73 | --- 74 | spring: 75 | profiles: kubernetes 76 | data: 77 | neo4j: 78 | uri: "bolt://social-network-neo4j:7687" 79 | cloud: 80 | kubernetes.discovery.enabled: true 81 | kubernetes.ribbon.enabled: true 82 | kubernetes.enabled: true 83 | stream: 84 | kafka: 85 | binder: 86 | brokers: "PLAINTEXT://kafka:9092" 87 | ribbon.eureka.enabled: false 88 | eureka.client.enabled: false 89 | prometheus.push.enabled: false 90 | --- 91 | spring: 92 | profiles: test 93 | cloud: 94 | kubernetes.discovery.enabled: false 95 | kubernetes.ribbon.enabled: false 96 | kubernetes.enabled: false 97 | data: 98 | neo4j: 99 | embedded: 100 | enabled: true 101 | prometheus.enabled: false -------------------------------------------------------------------------------- /Chapter09/social-network/recommendation-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: default,development,docker,kubernetes,test 3 | application: 4 | name: recommendation-service 5 | management: 6 | endpoints: 7 | web: 8 | exposure: 9 | include: "*" 10 | enabled-by-default: true 11 | metrics: 12 | tags: 13 | application: ${spring.application.name} 14 | environment: ${spring.profiles} 15 | export: 16 | prometheus: 17 | descriptions: true 18 | enabled: ${prometheus.enabled:true} # Whether exporting of metrics to Prometheus is enabled. 19 | step: 1m # Step size (i.e. reporting frequency) to use. 20 | pushgateway: 21 | base-url: ${prometheus.url:localhost:9091} # Base URL for the Pushgateway. 22 | enabled: ${prometheus.push.enabled:true} # Enable publishing via a Prometheus Pushgateway. 23 | job: ${spring.application.name} # Job identifier for this application instance. 24 | push-rate: 1m # Frequency with which to push metrics. 25 | shutdown-operation: push # Operation that should be performed on shutdown. 26 | -------------------------------------------------------------------------------- /Chapter09/social-network/temp.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "Namespace", 4 | "metadata": { 5 | "creationTimestamp": "2020-02-16T23:30:39Z", 6 | "deletionTimestamp": "2020-02-16T23:50:20Z", 7 | "name": "social-network", 8 | "resourceVersion": "2046204", 9 | "selfLink": "/api/v1/namespaces/social-network", 10 | "uid": "05add87d-e340-4f34-8523-ec43f9f8fbe3" 11 | }, 12 | "spec": { 13 | "finalizers": [] 14 | }, 15 | "status": { 16 | "phase": "Terminating" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.1-jre-slim-sid 2 | 3 | CMD ["/usr/bin/java", "-jar", "/usr/share/myservice/myservice.jar"] 4 | 5 | # Add the service itself 6 | ARG JAR_FILE 7 | COPY target/${JAR_FILE} /usr/share/myservice/myservice.jar -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/src/main/java/io/example/UserServiceApplication.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | import io.example.domain.User; 4 | import org.springframework.boot.WebApplicationType; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.builder.SpringApplicationBuilder; 7 | import org.springframework.cloud.stream.annotation.EnableBinding; 8 | import org.springframework.cloud.stream.messaging.Source; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * The microservice that manages the {@link User} domain. 13 | * 14 | * @author Kenny Bastani 15 | */ 16 | @SpringBootApplication 17 | public class UserServiceApplication { 18 | 19 | public static void main(String[] args) { 20 | new SpringApplicationBuilder(UserServiceApplication.class).web(WebApplicationType.REACTIVE).run(args); 21 | } 22 | 23 | @Configuration 24 | @EnableBinding(Source.class) 25 | class StreamConfig { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/src/main/java/io/example/domain/DomainEvent.java: -------------------------------------------------------------------------------- 1 | package io.example.domain; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A domain event is an abstract class that describes a behavior within a domain. 7 | * 8 | * @param is the type of domain object that this event applies to. 9 | * @param is the type of identity for the domain event. 10 | * @author Kenny Bastani 11 | */ 12 | public abstract class DomainEvent implements Serializable { 13 | 14 | private ID id; 15 | private Long createdAt; 16 | private Long lastModified; 17 | 18 | public DomainEvent() { 19 | } 20 | 21 | public ID getId() { 22 | return id; 23 | } 24 | 25 | public void setId(ID id) { 26 | this.id = id; 27 | } 28 | 29 | public Long getCreatedAt() { 30 | return createdAt; 31 | } 32 | 33 | public void setCreatedAt(Long createdAt) { 34 | this.createdAt = createdAt; 35 | } 36 | 37 | public Long getLastModified() { 38 | return lastModified; 39 | } 40 | 41 | public void setLastModified(Long lastModified) { 42 | this.lastModified = lastModified; 43 | } 44 | 45 | public abstract T getSubject(); 46 | 47 | public abstract void setSubject(T subject); 48 | 49 | public abstract EventType getEventType(); 50 | 51 | public abstract void setEventType(EventType eventType); 52 | 53 | @Override 54 | public String toString() { 55 | return "DomainEvent{" + 56 | "id=" + id + 57 | ", createdAt=" + createdAt + 58 | ", lastModified=" + lastModified + 59 | '}'; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/src/main/java/io/example/domain/EventType.java: -------------------------------------------------------------------------------- 1 | package io.example.domain; 2 | 3 | /** 4 | * The type of events that affect the state of a {@link User}. 5 | * 6 | * @author Kenny Bastani 7 | */ 8 | public enum EventType { 9 | USER_CREATED, 10 | USER_UPDATED 11 | } 12 | -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/src/main/java/io/example/domain/User.java: -------------------------------------------------------------------------------- 1 | package io.example.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.relational.core.mapping.Column; 6 | import org.springframework.data.relational.core.mapping.Table; 7 | 8 | import java.sql.Timestamp; 9 | import java.util.Objects; 10 | 11 | /** 12 | * The {@link User} domain entity representing the identity of a user. 13 | * 14 | * @author Kenny Bastani 15 | */ 16 | @Table(value = "users") 17 | public class User { 18 | 19 | @Id 20 | private Long id; 21 | 22 | @Column(value = "first_name") 23 | private String firstName; 24 | 25 | @Column(value = "last_name") 26 | private String lastName; 27 | 28 | @Column(value = "created_at") 29 | private Timestamp createdAt; 30 | 31 | @Column(value = "updated_at") 32 | private Timestamp updatedAt; 33 | 34 | public User() { 35 | } 36 | 37 | 38 | public User(String firstName, String lastName) { 39 | this.firstName = firstName; 40 | this.lastName = lastName; 41 | } 42 | 43 | public User(Long id, String firstName, String lastName) { 44 | this.id = id; 45 | this.firstName = firstName; 46 | this.lastName = lastName; 47 | } 48 | 49 | @JsonProperty("id") 50 | public Long getId() { 51 | return id; 52 | } 53 | 54 | public void setId(Long id) { 55 | this.id = id; 56 | } 57 | 58 | public Timestamp getCreatedAt() { 59 | return createdAt; 60 | } 61 | 62 | public void setCreatedAt(Timestamp createdAt) { 63 | this.createdAt = createdAt; 64 | } 65 | 66 | public Timestamp getLastModified() { 67 | return updatedAt; 68 | } 69 | 70 | public void setLastModified(Timestamp lastModified) { 71 | this.updatedAt = lastModified; 72 | } 73 | 74 | public String getFirstName() { 75 | return firstName; 76 | } 77 | 78 | public void setFirstName(String firstName) { 79 | this.firstName = firstName; 80 | } 81 | 82 | public String getLastName() { 83 | return lastName; 84 | } 85 | 86 | public void setLastName(String lastName) { 87 | this.lastName = lastName; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return "User{" + 93 | "id=" + id + 94 | ", firstName='" + firstName + '\'' + 95 | ", lastName='" + lastName + '\'' + 96 | ", createdAt=" + createdAt + 97 | ", updatedAt=" + updatedAt + 98 | '}'; 99 | } 100 | 101 | @Override 102 | public boolean equals(Object o) { 103 | if (this == o) return true; 104 | if (o == null || getClass() != o.getClass()) return false; 105 | User user = (User) o; 106 | return Objects.equals(id, user.id) && 107 | Objects.equals(firstName, user.firstName) && 108 | Objects.equals(lastName, user.lastName) && 109 | Objects.equals(createdAt, user.createdAt) && 110 | Objects.equals(updatedAt, user.updatedAt); 111 | } 112 | 113 | @Override 114 | public int hashCode() { 115 | 116 | return Objects.hash(id, firstName, lastName, createdAt, updatedAt); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/src/main/java/io/example/domain/UserEvent.java: -------------------------------------------------------------------------------- 1 | package io.example.domain; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * An event that encapsulates a state transition for the {@link User} domain object. 7 | * 8 | * @link Kenny Bastani 9 | */ 10 | public class UserEvent extends DomainEvent { 11 | 12 | private User subject; 13 | private EventType eventType; 14 | 15 | public UserEvent() { 16 | this.setId(UUID.randomUUID().hashCode()); 17 | } 18 | 19 | public UserEvent(User subject, EventType eventType) { 20 | this(); 21 | this.subject = subject; 22 | this.eventType = eventType; 23 | } 24 | 25 | public UserEvent(EventType userCreated) { 26 | this.eventType = userCreated; 27 | } 28 | 29 | @Override 30 | public User getSubject() { 31 | return subject; 32 | } 33 | 34 | @Override 35 | public void setSubject(User subject) { 36 | this.subject = subject; 37 | } 38 | 39 | @Override 40 | public EventType getEventType() { 41 | return eventType; 42 | } 43 | 44 | @Override 45 | public void setEventType(EventType eventType) { 46 | this.eventType = eventType; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "UserEvent{" + 52 | "subject=" + subject + 53 | ", eventType=" + eventType + 54 | '}'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/src/main/java/io/example/domain/UserRepository.java: -------------------------------------------------------------------------------- 1 | package io.example.domain; 2 | 3 | import org.springframework.data.r2dbc.repository.R2dbcRepository; 4 | import org.springframework.data.r2dbc.repository.query.Query; 5 | import reactor.core.publisher.Mono; 6 | 7 | /** 8 | * The repository for managing {@link User} data. 9 | * 10 | * @author Kenny Bastani 11 | */ 12 | public interface UserRepository extends R2dbcRepository { 13 | 14 | @Query("SELECT * FROM users u WHERE u.id = $1 LIMIT 1") 15 | Mono getUser(Long id); 16 | } 17 | -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: default,development,test,docker,kubernetes 3 | application: 4 | name: user-service 5 | management: 6 | endpoints: 7 | web: 8 | exposure: 9 | include: "*" 10 | enabled-by-default: true 11 | metrics: 12 | tags: 13 | application: ${spring.application.name} 14 | environment: ${spring.profiles} 15 | export: 16 | prometheus: 17 | descriptions: true 18 | enabled: ${prometheus.enabled:true} # Whether exporting of metrics to Prometheus is enabled. 19 | step: 1m # Step size (i.e. reporting frequency) to use. 20 | pushgateway: 21 | base-url: ${prometheus.url:localhost:9091} # Base URL for the Pushgateway. 22 | enabled: ${prometheus.push.enabled:true} # Enable publishing via a Prometheus Pushgateway. 23 | job: ${spring.application.name} # Job identifier for this application instance. 24 | push-rate: 1m # Frequency with which to push metrics. 25 | shutdown-operation: push # Operation that should be performed on shutdown. 26 | --- -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/src/main/resources/db/changelog/db.changelog-master.yaml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - changeSet: 3 | id: 1 4 | author: user-service 5 | changes: 6 | - createTable: 7 | tableName: users 8 | columns: 9 | - column: 10 | name: id 11 | type: BIGINT 12 | autoIncrement: true 13 | constraints: 14 | primaryKey: true 15 | nullable: false 16 | - column: 17 | name: first_name 18 | type: VARCHAR 19 | constraints: 20 | nullable: false 21 | - column: 22 | name: last_name 23 | type: VARCHAR 24 | constraints: 25 | nullable: false 26 | - column: 27 | name: created_at 28 | type: java.sql.Types.TIMESTAMP 29 | defaultValueComputed: NOW() 30 | constraints: 31 | nullable: false 32 | - column: 33 | name: updated_at 34 | type: java.sql.Types.TIMESTAMP 35 | defaultValueComputed: NOW() 36 | valueComputed: NOW() 37 | constraints: 38 | nullable: false -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/src/test/java/io/example/AbstractIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.junit.ClassRule; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.boot.test.util.TestPropertyValues; 8 | import org.springframework.context.ApplicationContextInitializer; 9 | import org.springframework.context.ConfigurableApplicationContext; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | import org.testcontainers.containers.GenericContainer; 14 | import org.testcontainers.containers.KafkaContainer; 15 | 16 | @RunWith(SpringJUnit4ClassRunner.class) 17 | @SpringBootTest(classes = UserServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 18 | @ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class) 19 | @ActiveProfiles("test") 20 | public abstract class AbstractIntegrationTest { 21 | 22 | @ClassRule 23 | public static KafkaContainer kafka = new KafkaContainer(); 24 | 25 | @ClassRule 26 | public static GenericContainer postgres = new GenericContainer("postgres:9.6.8") 27 | .withExposedPorts(5432) 28 | .withEnv("POSTGRES_PASSWORD", "password") 29 | .withEnv("POSTGRES_USER", "postgres"); 30 | 31 | 32 | static { 33 | postgres.start(); 34 | } 35 | 36 | public static class Initializer implements ApplicationContextInitializer { 37 | @Override 38 | public void initialize(@NotNull ConfigurableApplicationContext configurableApplicationContext) { 39 | String jdbcUrl = String.format("jdbc:postgresql://%s:%d/%s", postgres.getContainerIpAddress(), 40 | postgres.getMappedPort(5432), "postgres"); 41 | TestPropertyValues values = TestPropertyValues.of( 42 | "postgres.host=" + postgres.getContainerIpAddress(), 43 | "postgres.port=" + postgres.getMappedPort(5432), 44 | "postgres.url=" + jdbcUrl, 45 | "spring.datasource.url=" + jdbcUrl, 46 | "spring.cloud.stream.kafka.binder.brokers=" + kafka.getBootstrapServers(), 47 | "eureka.client.enabled=false"); 48 | values.applyTo(configurableApplicationContext); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Chapter09/social-network/user-service/src/test/java/io/example/AbstractUnitTest.java: -------------------------------------------------------------------------------- 1 | package io.example; 2 | 3 | import io.example.config.DataSourceConfiguration; 4 | import io.example.domain.UserService; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.junit.ClassRule; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 9 | import org.springframework.boot.test.util.TestPropertyValues; 10 | import org.springframework.context.ApplicationContextInitializer; 11 | import org.springframework.context.ConfigurableApplicationContext; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.test.context.ContextConfiguration; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | import org.testcontainers.containers.GenericContainer; 16 | 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | @ContextConfiguration(initializers = AbstractUnitTest.Initializer.class, classes = { 19 | DataSourceConfiguration.class, 20 | DataSourceAutoConfiguration.class, 21 | UserService.class 22 | }) 23 | @ActiveProfiles("test") 24 | public abstract class AbstractUnitTest { 25 | 26 | @ClassRule 27 | public static GenericContainer postgres = new GenericContainer("postgres:9.6.8") 28 | .withExposedPorts(5432) 29 | .withEnv("POSTGRES_PASSWORD", "password") 30 | .withEnv("POSTGRES_USER", "postgres"); 31 | 32 | 33 | static { 34 | postgres.start(); 35 | } 36 | 37 | public static class Initializer implements ApplicationContextInitializer { 38 | @Override 39 | public void initialize(@NotNull ConfigurableApplicationContext configurableApplicationContext) { 40 | String jdbcUrl = String.format("jdbc:postgresql://%s:%d/%s", postgres.getContainerIpAddress(), 41 | postgres.getMappedPort(5432), "postgres"); 42 | 43 | TestPropertyValues values = TestPropertyValues.of( 44 | "postgres.host=" + postgres.getContainerIpAddress(), 45 | "postgres.port=" + postgres.getMappedPort(5432), 46 | "postgres.url=" + jdbcUrl, 47 | "postgres.database-name=postgres", 48 | "spring.application.name=user-service", 49 | "spring.datasource.data-username=postgres", 50 | "spring.datasource.data-password=password", 51 | "spring.datasource.url=" + jdbcUrl, 52 | "eureka.client.enabled=false"); 53 | 54 | values.applyTo(configurableApplicationContext); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Chapter10/azure-vote.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: azure-vote-back 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: azure-vote-back 10 | template: 11 | metadata: 12 | labels: 13 | app: azure-vote-back 14 | spec: 15 | containers: 16 | - name: azure-vote-back 17 | image: redis 18 | resources: 19 | requests: 20 | cpu: 10m 21 | memory: 128Mi 22 | limits: 23 | cpu: 20m 24 | memory: 256Mi 25 | ports: 26 | - containerPort: 6379 27 | name: redis 28 | --- 29 | apiVersion: v1 30 | kind: Service 31 | metadata: 32 | name: azure-vote-back 33 | spec: 34 | ports: 35 | - port: 6379 36 | selector: 37 | app: azure-vote-back 38 | --- 39 | apiVersion: apps/v1 40 | kind: Deployment 41 | metadata: 42 | name: azure-vote-front 43 | spec: 44 | replicas: 1 45 | selector: 46 | matchLabels: 47 | app: azure-vote-front 48 | template: 49 | metadata: 50 | labels: 51 | app: azure-vote-front 52 | spec: 53 | containers: 54 | - name: azure-vote-front 55 | image: microsoft/azure-vote-front:v1 56 | resources: 57 | requests: 58 | cpu: 10m 59 | memory: 128Mi 60 | limits: 61 | cpu: 20m 62 | memory: 256Mi 63 | ports: 64 | - containerPort: 80 65 | env: 66 | - name: REDIS 67 | value: "azure-vote-back" 68 | --- 69 | apiVersion: v1 70 | kind: Service 71 | metadata: 72 | name: azure-vote-front 73 | spec: 74 | type: LoadBalancer 75 | ports: 76 | - port: 80 77 | selector: 78 | app: azure-vote-front 79 | -------------------------------------------------------------------------------- /Chapter10/clusterRole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: readOnly 5 | rules: 6 | - apiGroups: [""] 7 | resources: ["pods"] 8 | verbs: ["get", "watch", "list"] -------------------------------------------------------------------------------- /Chapter10/clusterRoleBinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: readOnlyBinding 5 | roleRef: 6 | kind: ClusterRole 7 | name: readOnly 8 | apiGroup: rbac.authorization.k8s.io 9 | subjects: 10 | - kind: Group 11 | apiGroup: rbac.authorization.k8s.io 12 | name: "eecb806a-35c4-4a0d-a8dc-4cb983c5371b" -------------------------------------------------------------------------------- /Chapter10/create_generic_secrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | kubectl create secret generic my-api-secret-literal --from-literal=url=https://my-secret-url-location.topsecret.com --from-literal=token='/x~Lhx\nAz!,;.Vk%[#n+";9p%jGF6[' 3 | -------------------------------------------------------------------------------- /Chapter10/destinationRule.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: DestinationRule 3 | metadata: 4 | name: default 5 | spec: 6 | host: "*.local" 7 | trafficPolicy: 8 | tls: 9 | mode: ISTIO_MUTUAL -------------------------------------------------------------------------------- /Chapter10/gen_secrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "https://my-secret-url-location.topsecret.com" > secreturl.txt 3 | echo '/x~Lhx\nAz!,;.Vk%[#n+";9p%jGF6[' > secrettoken.txt 4 | -------------------------------------------------------------------------------- /Chapter10/httpbin.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Istio Authors 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 | ################################################################################################## 16 | # httpbin service 17 | ################################################################################################## 18 | apiVersion: v1 19 | kind: ServiceAccount 20 | metadata: 21 | name: httpbin 22 | --- 23 | apiVersion: v1 24 | kind: Service 25 | metadata: 26 | name: httpbin 27 | labels: 28 | app: httpbin 29 | spec: 30 | ports: 31 | - name: http 32 | port: 8000 33 | targetPort: 80 34 | selector: 35 | app: httpbin 36 | --- 37 | apiVersion: apps/v1 38 | kind: Deployment 39 | metadata: 40 | name: httpbin 41 | spec: 42 | replicas: 1 43 | selector: 44 | matchLabels: 45 | app: httpbin 46 | version: v1 47 | template: 48 | metadata: 49 | labels: 50 | app: httpbin 51 | version: v1 52 | spec: 53 | serviceAccountName: httpbin 54 | containers: 55 | - image: docker.io/kennethreitz/httpbin 56 | imagePullPolicy: IfNotPresent 57 | name: httpbin 58 | ports: 59 | - containerPort: 80 60 | -------------------------------------------------------------------------------- /Chapter10/kv-flexvol-installer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: kv 5 | --- 6 | apiVersion: apps/v1 7 | kind: DaemonSet 8 | metadata: 9 | labels: 10 | app: keyvault-flexvolume 11 | name: keyvault-flexvolume 12 | namespace: kv 13 | spec: 14 | selector: 15 | matchLabels: 16 | app: keyvault-flexvolume 17 | updateStrategy: 18 | type: RollingUpdate 19 | template: 20 | metadata: 21 | labels: 22 | app: keyvault-flexvolume 23 | spec: 24 | tolerations: 25 | containers: 26 | - name: flexvol-driver-installer 27 | image: "mcr.microsoft.com/k8s/flexvolume/keyvault-flexvolume:v0.0.15" 28 | imagePullPolicy: Always 29 | resources: 30 | requests: 31 | cpu: 50m 32 | memory: 100Mi 33 | limits: 34 | cpu: 50m 35 | memory: 100Mi 36 | env: 37 | # if you have used flex before on your cluster, use same directory 38 | # set TARGET_DIR env var and mount the same directory of the container 39 | - name: TARGET_DIR 40 | value: "/etc/kubernetes/volumeplugins" 41 | volumeMounts: 42 | - mountPath: "/etc/kubernetes/volumeplugins" 43 | name: volplugins 44 | volumes: 45 | - hostPath: 46 | # Modify this directory if your nodes are using a different one 47 | # default kubernetes: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec" 48 | # google cloud kubernetes: "/home/kubernetes/flexvolume" 49 | # below is Azure default 50 | path: "/etc/kubernetes/volumeplugins" 51 | name: volplugins 52 | nodeSelector: 53 | beta.kubernetes.io/os: linux 54 | -------------------------------------------------------------------------------- /Chapter10/mtls_policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: authentication.istio.io/v1alpha1 2 | kind: MeshPolicy 3 | metadata: 4 | name: default 5 | spec: 6 | peers: 7 | - mtls: {} -------------------------------------------------------------------------------- /Chapter10/myfirstsecret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: myapiurltoken-yaml 5 | type: Opaque 6 | data: 7 | url: aHR0cHM6Ly9teS1zZWNyZXQtdXJsLWxvY2F0aW9uLnRvcHNlY3JldC5jb20K 8 | token: c3VwZXJTZWNyZXRUb2tlbgo= 9 | -------------------------------------------------------------------------------- /Chapter10/pod-with-env-secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: secret-using-env 5 | spec: 6 | containers: 7 | - name: nginx 8 | image: nginx 9 | env: 10 | - name: SECRET_URL 11 | valueFrom: 12 | secretKeyRef: 13 | name: myapi-url-token 14 | key: secreturl.txt 15 | - name: SECRET_TOKEN 16 | valueFrom: 17 | secretKeyRef: 18 | name: myapi-url-token 19 | key: secrettoken.txt 20 | restartPolicy: Never -------------------------------------------------------------------------------- /Chapter10/pod-with-vol-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: secret-using-volume 5 | spec: 6 | containers: 7 | - name: nginx 8 | image: nginx 9 | volumeMounts: 10 | - name: secretvolume 11 | mountPath: "/etc/secrets" 12 | readOnly: true 13 | volumes: 14 | - name: secretvolume 15 | secret: 16 | secretName: myapi-url-token 17 | -------------------------------------------------------------------------------- /Chapter10/pod_secret_flex.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: nginx-secret-flex 5 | spec: 6 | containers: 7 | - name: nginx 8 | image: nginx 9 | volumeMounts: 10 | - name: test 11 | mountPath: /etc/secret/ 12 | readOnly: true 13 | volumes: 14 | - name: test 15 | flexVolume: 16 | driver: "azure/kv" 17 | secretRef: 18 | name: kvcreds 19 | options: 20 | keyvaultname: 21 | keyvaultobjectnames: k8s-secret-demo 22 | keyvaultobjecttypes: secret 23 | tenantid: "" -------------------------------------------------------------------------------- /Chapter10/role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: deleteRole 5 | namespace: delete-access 6 | rules: 7 | - apiGroups: [""] 8 | resources: ["pods"] 9 | verbs: ["delete"] -------------------------------------------------------------------------------- /Chapter10/roleBinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: deleteBinding 5 | namespace: delete-access 6 | roleRef: 7 | kind: Role 8 | name: deleteRole 9 | apiGroup: rbac.authorization.k8s.io 10 | subjects: 11 | - kind: User 12 | apiGroup: rbac.authorization.k8s.io 13 | name: "tim@handsonaksdemooutlook.onmicrosoft.com" -------------------------------------------------------------------------------- /Chapter10/secrettoken.txt: -------------------------------------------------------------------------------- 1 | superSecretToken 2 | -------------------------------------------------------------------------------- /Chapter10/secreturl.txt: -------------------------------------------------------------------------------- 1 | https://my-secret-url-location.topsecret.com 2 | -------------------------------------------------------------------------------- /Chapter10/sleep.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Istio Authors 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 | ################################################################################################## 16 | # Sleep service 17 | ################################################################################################## 18 | apiVersion: v1 19 | kind: ServiceAccount 20 | metadata: 21 | name: sleep 22 | --- 23 | apiVersion: v1 24 | kind: Service 25 | metadata: 26 | name: sleep 27 | labels: 28 | app: sleep 29 | spec: 30 | ports: 31 | - port: 80 32 | name: http 33 | selector: 34 | app: sleep 35 | --- 36 | apiVersion: apps/v1 37 | kind: Deployment 38 | metadata: 39 | name: sleep 40 | spec: 41 | replicas: 1 42 | selector: 43 | matchLabels: 44 | app: sleep 45 | template: 46 | metadata: 47 | labels: 48 | app: sleep 49 | spec: 50 | serviceAccountName: sleep 51 | containers: 52 | - name: sleep 53 | image: governmentpaas/curl-ssl 54 | command: ["/bin/sleep", "3650d"] 55 | imagePullPolicy: IfNotPresent 56 | volumeMounts: 57 | - mountPath: /etc/sleep/tls 58 | name: secret-volume 59 | volumes: 60 | - name: secret-volume 61 | secret: 62 | secretName: sleep-secret 63 | optional: true 64 | --- 65 | -------------------------------------------------------------------------------- /Chapter10/test_mtls.sh: -------------------------------------------------------------------------------- 1 | for from in "foo" "bar" "legacy" 2 | do 3 | for to in "foo" "bar" "legacy" 4 | do 5 | kubectl exec $(kubectl get pod -l app=sleep -n ${from} \ 6 | -o jsonpath={.items..metadata.name}) -c sleep \ 7 | -n ${from} -- curl http://httpbin.${to}:8000/ip \ 8 | -s -o /dev/null -w "sleep.${from} to \ 9 | httpbin.${to}: %{http_code}\n"; 10 | done 11 | done 12 | -------------------------------------------------------------------------------- /Chapter11/http/.dockerignore: -------------------------------------------------------------------------------- 1 | local.settings.json -------------------------------------------------------------------------------- /Chapter11/http/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | csx 4 | .vs 5 | edge 6 | Publish 7 | 8 | *.user 9 | *.suo 10 | *.cscfg 11 | *.Cache 12 | project.lock.json 13 | 14 | /packages 15 | /TestResults 16 | 17 | /tools/NuGet.exe 18 | /App_Data 19 | /secrets 20 | /data 21 | .secrets 22 | appsettings.json 23 | local.settings.json 24 | 25 | node_modules 26 | dist 27 | 28 | # Local python packages 29 | .python_packages/ 30 | 31 | # Python Environments 32 | .env 33 | .venv 34 | env/ 35 | venv/ 36 | ENV/ 37 | env.bak/ 38 | venv.bak/ 39 | 40 | # Byte-compiled / optimized / DLL files 41 | __pycache__/ 42 | *.py[cod] 43 | *$py.class -------------------------------------------------------------------------------- /Chapter11/http/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } -------------------------------------------------------------------------------- /Chapter11/http/Dockerfile: -------------------------------------------------------------------------------- 1 | # To enable ssh & remote debugging on app service change the base image to the one below 2 | # FROM mcr.microsoft.com/azure-functions/python:2.0-python3.6-appservice 3 | FROM mcr.microsoft.com/azure-functions/python:2.0-python3.6 4 | 5 | ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ 6 | AzureFunctionsJobHost__Logging__Console__IsEnabled=true 7 | 8 | COPY requirements.txt / 9 | RUN pip install -r /requirements.txt 10 | 11 | COPY . /home/site/wwwroot -------------------------------------------------------------------------------- /Chapter11/http/azure-functions-core-tools/.telemetry/20200219024525_e379261df05b40d1906718fb3bb1238a.trn: -------------------------------------------------------------------------------- 1 | https://dc.services.visualstudio.com/v2/track 2 | Content-Type:application/x-json-stream 3 | Content-Encoding:gzip 4 | 5 | H4sIAAAAAAAAA4xUy27bOBT9FULdzACmRnzp4V0Qt2iQpgkSdxazGfBxZRORRYOigqRF/30u5bTxDLoYLy+PDu950N+KUR+gWBc33sYwhT6VF8fj4K1OPoxX4+R3+zSVqnaiq3tpjaqlErx1rDNMtZVhlrdVW75/gjEVqyL5hY1XvKIVp6zbVnwt1ZqrkslGSS7+QpS/hhdE/WClmZZmXpqJ6Tlz5tS7qVh/K7QvHTx5C2WY/oQ44YJIMpt5TDPC8NgOYXZlDAPg5kmPNu9yjP5JJ6CKGVYBGGoarqgU0tCuaRpaG9HWTWNbqdmJZoIpk5fe5SX/92d+TBBHPZSTe3zbz4U0QrJrXjJWVpSjDfI/+DE4+HyKAQXeaLv3I5S/jWGE34vvq8LppLMBRk+wfTlm3OL3Js9Xy3jzCnmCWKz56keqNkSgKYRhWpN+Hi15nA3eCQkm4uA4hBf8/hjDEWLysLj8yRtL3tbnJW8Qczun45zIPTgfwSbIznzQwwQ5zelhthY96+cBx9s456kNh4Me3auuX937KpRcbRAgbAeNcLJm2qiGW2Zr0zhlXNsK1dfGaXTb1YJX2CFjjJbK1lYILo3sVKuQbxPsI0RyGcakkTeerXiPHcFmkiv3szEla8tK0uc6p7GIvocB0EpEYHfMsEi7sPkdvIq4/ilis2g4HSLsco4R8yB3Ou3JRz3tESxZm1dsW92wygjQrjLcqqrttWDGSScbLl1fO1PVrmF9rfB1uL51ou0b5kQORke8GEuCwSyJriLs/JTiy+lwgvcxhnOdtw/kbtCpD/GA009+nJ9P07dAF9k4hNwgslTciKpuO6Gpq5qeSsMEVrwTtGZ91zN0vxFq6Qn0/vk2Ptjoj+k+hHR2810MbrbpX81pSo4m4OF1tm04O1s2I6qs8EWwSjCqv84RyDsh6JclHfJwc0e2+5l8AENqwvma12smyJftJcl/Lki6G4LRw9+DHnez3uV8Pv9xkZ/LAVNEtgPqWxqdk9/qR8CLGees+46/fwAAAP//AwAQRxeM+AQAAA== -------------------------------------------------------------------------------- /Chapter11/http/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "extensionBundle": { 4 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 5 | "version": "[1.*, 2.0.0)" 6 | } 7 | } -------------------------------------------------------------------------------- /Chapter11/http/python-http/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import azure.functions as func 4 | 5 | 6 | def main(req: func.HttpRequest) -> func.HttpResponse: 7 | logging.info('Python HTTP trigger function processed a request.') 8 | 9 | name = req.params.get('name') 10 | if not name: 11 | try: 12 | req_body = req.get_json() 13 | except ValueError: 14 | pass 15 | else: 16 | name = req_body.get('name') 17 | 18 | if name: 19 | return func.HttpResponse(f"Hello {name}!") 20 | else: 21 | return func.HttpResponse( 22 | "Please pass a name on the query string or in the request body", 23 | status_code=400 24 | ) 25 | -------------------------------------------------------------------------------- /Chapter11/http/python-http/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptFile": "__init__.py", 3 | "bindings": [ 4 | { 5 | "authLevel": "anonymous", 6 | "type": "httpTrigger", 7 | "direction": "in", 8 | "name": "req", 9 | "methods": [ 10 | "get", 11 | "post" 12 | ] 13 | }, 14 | { 15 | "type": "http", 16 | "direction": "out", 17 | "name": "$return" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /Chapter11/http/requirements.txt: -------------------------------------------------------------------------------- 1 | azure-functions -------------------------------------------------------------------------------- /Chapter11/js-queue/.dockerignore: -------------------------------------------------------------------------------- 1 | local.settings.json -------------------------------------------------------------------------------- /Chapter11/js-queue/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | csx 4 | .vs 5 | edge 6 | Publish 7 | 8 | *.user 9 | *.suo 10 | *.cscfg 11 | *.Cache 12 | project.lock.json 13 | 14 | /packages 15 | /TestResults 16 | 17 | /tools/NuGet.exe 18 | /App_Data 19 | /secrets 20 | /data 21 | .secrets 22 | appsettings.json 23 | local.settings.json 24 | 25 | node_modules 26 | dist 27 | 28 | # Local python packages 29 | .python_packages/ 30 | 31 | # Python Environments 32 | .env 33 | .venv 34 | env/ 35 | venv/ 36 | ENV/ 37 | env.bak/ 38 | venv.bak/ 39 | 40 | # Byte-compiled / optimized / DLL files 41 | __pycache__/ 42 | *.py[cod] 43 | *$py.class -------------------------------------------------------------------------------- /Chapter11/js-queue/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } -------------------------------------------------------------------------------- /Chapter11/js-queue/Dockerfile: -------------------------------------------------------------------------------- 1 | # To enable ssh & remote debugging on app service change the base image to the one below 2 | # FROM mcr.microsoft.com/azure-functions/node:2.0-appservice 3 | FROM mcr.microsoft.com/azure-functions/node:2.0 4 | 5 | ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ 6 | AzureFunctionsJobHost__Logging__Console__IsEnabled=true 7 | 8 | COPY . /home/site/wwwroot 9 | 10 | RUN cd /home/site/wwwroot && \ 11 | npm install -------------------------------------------------------------------------------- /Chapter11/js-queue/azure-functions-core-tools/.telemetry/20200220040920_1d8e395b746346be87941c639630ab9c.trn: -------------------------------------------------------------------------------- 1 | https://dc.services.visualstudio.com/v2/track 2 | Content-Type:application/x-json-stream 3 | Content-Encoding:gzip 4 | 5 | H4sIAAAAAAAAA4xUTW/cNhD9K4R6aQFL5Zckam+GnaCG48RYb3ropeDHaJewVhQoauE0yH/PUGs326KH6jjz5mnmzRt+LUZ9hGJTPHgbwxz6VF1P0+CtTj6Md+Ps94c0V3XjRNf00pq6kbXgyrHOsFpRwyxXVFXvTjCm4qpIfmXjlNOS8pLTHZUb2m1YV3WqFlTKPxDl7+ELot5Yy0xbZt4yE5eXzJlT7+di87XQvnJw8haqMP8OccYGkWQxy5gWhGHaDmFxVQwDYOdJjzb3MkV/0glK2WvDe6pK60xbyo71pTbWlS1jXVuLTpjWnWlmmDN55R2W//8yPyaIox6q2T3/6M+FNEKyG14xVtGSM9nKf+HH4ODjeQ044IO2Bz9C9fMYRvil+HZVOJ10FsDoGXZfpoxb9b7N8as1fPsKOUEsNvzqbas2RChTCMO8If0yWvK8GPwnJJhJhGM4AdZPMUwQk4dV5fucH8iPAT74cXkhdUWxe0YFK/VfSwTykxDl51V78vTwSHaHhbwHQxrC+YY3GybI590NyUbAX3x6uiBkqqJZgg/e2Iswr3iL0ccY3GLTPxJtxZnKJTYcj3p0r2L91zD+2mbnviK2a/genD6HEXCzxIjakUedDuQ3PR8QJiTveiVUzWulm0Z0Tjc9d6qnLeWd7TrrJHK0DoEMWsZFL02tOmikEI7nzm6DfYZIbsKYNG4Pt1C818MM5+EfB536EI9vcmbVdZzhXYzhEjpF6P3Lp/hko5/SNoR0ybOkaUlkC85HsAncRW4/BKOHPwc97he9z6N//PX6TeMtDIAewSAehRky/tVk5O42j287aIWTDdOmbrlltkFT18YpJeq+MU6rpnWN4FRyYYzRsraNFYJLI7ta1Vn2+WmxFu+mXwZk3MUFzjPiHtDl6KzVkvOkbU5Ati9Z7wt63iikKWuw+Ahwa0ulwJaCMtlRp1hvLVZs0Wn4tpA79/fNV6uRypdG5iM54ojoyyMSrz7O6J1+BnQQZ5S13/D7DgAA//8DAMgYUm/uBAAA -------------------------------------------------------------------------------- /Chapter11/js-queue/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "extensionBundle": { 4 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 5 | "version": "[1.*, 2.0.0)" 6 | } 7 | } -------------------------------------------------------------------------------- /Chapter11/js-queue/js-queue/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "name": "myQueueItem", 5 | "type": "queueTrigger", 6 | "direction": "in", 7 | "queueName": "function", 8 | "connection": "QueueConnString" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Chapter11/js-queue/js-queue/index.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, myQueueItem) { 2 | context.log('JavaScript queue trigger function processed work item', myQueueItem); 3 | }; -------------------------------------------------------------------------------- /Chapter11/js-queue/js-queue/readme.md: -------------------------------------------------------------------------------- 1 | # QueueTrigger - JavaScript 2 | 3 | The `QueueTrigger` makes it incredibly easy to react to new Queues inside of Azure Queue Storage. This sample demonstrates a simple use case of processing data from a given Queue using C#. 4 | 5 | ## How it works 6 | 7 | For a `QueueTrigger` to work, you provide a path which dictates where the queue messages are located inside your container. 8 | 9 | ## Learn more 10 | 11 | Documentation -------------------------------------------------------------------------------- /Chapter11/js-queue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "version": "", 4 | "description": "", 5 | "scripts" : { 6 | "test": "echo \"No tests yet...\"" 7 | }, 8 | "author": "" 9 | } -------------------------------------------------------------------------------- /Chapter11/sendMessages.py: -------------------------------------------------------------------------------- 1 | from azure.storage.common import CloudStorageAccount 2 | from azure.storage.queue import Queue, QueueService, QueueMessage 3 | 4 | queue_service = QueueService(connection_string="") 5 | 6 | 7 | for i in range(1, 1000): 8 | messagename="test" 9 | queue_service.put_message("function", messagename + str(i)) 10 | print ('Successfully added message: ', messagename + str(i)) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hands-On-Kubernetes-on-Azure---Second-Edition 2 | Hands-On Kubernetes on Azure - Second Edition 3 | --------------------------------------------------------------------------------