{"id":26725487,"url":"https://github.com/clarklindev/microservices-stephengrider-with-node-and-react","last_synced_at":"2026-04-12T00:32:53.921Z","repository":{"id":143235051,"uuid":"592710378","full_name":"clarklindev/microservices-stephengrider-with-node-and-react","owner":"clarklindev","description":"Build, deploy, and scale an E-Commerce app using Microservices built with Node, React, Docker and Kubernetes","archived":false,"fork":false,"pushed_at":"2025-06-06T01:18:35.000Z","size":104171,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-12T22:49:09.514Z","etag":null,"topics":["cookie-session","docker","express-validator","google-cloud","jwt-authentication","kubernetes","microservices","mongodb","mongoose","response-normalisation-strategies","skaffold","typescript"],"latest_commit_sha":null,"homepage":"https://www.udemy.com/course/microservices-with-node-js-and-react/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clarklindev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-01-24T11:12:36.000Z","updated_at":"2025-06-06T01:18:38.000Z","dependencies_parsed_at":"2024-10-25T10:08:51.612Z","dependency_job_id":"9600b78c-2ace-4d51-a509-d85306f3a2e1","html_url":"https://github.com/clarklindev/microservices-stephengrider-with-node-and-react","commit_stats":null,"previous_names":["clarklindev/tutorial-stephengrider-microservices-with-node-and-react","thiccwithc/microservices-stephengrider-with-node-and-react","clarklindev/microservices-stephengrider-with-node-and-react"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/clarklindev/microservices-stephengrider-with-node-and-react","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clarklindev%2Fmicroservices-stephengrider-with-node-and-react","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clarklindev%2Fmicroservices-stephengrider-with-node-and-react/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clarklindev%2Fmicroservices-stephengrider-with-node-and-react/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clarklindev%2Fmicroservices-stephengrider-with-node-and-react/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clarklindev","download_url":"https://codeload.github.com/clarklindev/microservices-stephengrider-with-node-and-react/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clarklindev%2Fmicroservices-stephengrider-with-node-and-react/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271411443,"owners_count":24754932,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-20T02:00:09.606Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cookie-session","docker","express-validator","google-cloud","jwt-authentication","kubernetes","microservices","mongodb","mongoose","response-normalisation-strategies","skaffold","typescript"],"created_at":"2025-03-27T21:19:09.053Z","updated_at":"2026-04-12T00:32:48.882Z","avatar_url":"https://github.com/clarklindev.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Microservices\n\n- Build, deploy, and scale an E-Commerce app using Microservices built with Node, React, Docker and Kubernetes\n\n- [microservices-with-node-js-and-react](https://www.udemy.com/course/microservices-with-node-js-and-react/)\n\n- given /exercise_files\n- NOTE: `section01-04-blog` is for sections 1-4\n- NOTE: `section05-ticketing` is for section05 and all sections that continue building on the project\n\n## installation \n- [install docker](https://github.com/clarklindev/docker-stephen-grider-basics-of-docker)\n\n- install wsl (ubuntu)\n\n```sh\nwsl --install\n```\n\u003cimg\nsrc='exercise_files/udemy-docker-section00-wsl-install.png'\nalt='udemy-docker-section00-wsl-install.png'\nwidth=600\n/\u003e\n\n## Table of contents\n\n### Microservices and Docker/Kubernetes/Skaffold\n\n- [Section 01 - Fundamental Ideas Around Microservices (46min)](#section-01---fundamental-ideas-around-microservices-46min)\n- [Section 02 - A Mini Microservices App (3hr35min)](#section-02---a-mini-microservices-app-3hr35min)\n- [Section 03 - Running Services with Docker (30min)](#section-03---running-services-with-docker-30min)\n- [Section 04 - Orchestrating Collections of Services with Kubernetes (3hr25min)](#section-04---orchestrating-collections-of-services-with-kubernetes-3hr25min)\n\n- [Section 05 - Architecture of Multiservice Apps (1hr6min)](#section-05---architecture-of-multiservice-apps-1hr6min)\n\n### Skaffold with Google Cloud Kubernetes\n\n- [Section 06 - Leveraging a Cloud Environment for Development (47min)](#section-06---leveraging-a-cloud-environment-for-development-47min)\n\n### Working with data and error handling\n\n- [Section 07 - Response Normalisation Strategies (1hr58min)](#section-07---response-normalisation-strategies-1hr58min)\n- [Section 08 - Database Management and Modeling (1hr27min)](#section-08---database-management-and-modeling-1hr27min)\n\n### Authentication\n\n- [Section 09 - Authentication Strategies and Options (2hr48min)](#section-09---authentication-strategies-and-options-2hr48min)\n\n### Testing Microservices\n\n- [Section 10 - Testing Isolated Microservices (1hr22min)](#section-10---testing-isolated-microservices-1hr22min)\n\n### Server-side Rendered (SSR) React app\n\n- [Section 11 - Integrating a Server Side Rendered React App (3hr01min)](#section-11---integrating-a-server-side-rendered-react-app-3hr01min)\n\n### Code-sharing and re-use\n\n- [Section 12 - Code Sharing and Re-use Between Services (52min)](#section-12---code-sharing-and-re-use-between-services-52min)\n\n### CRUD server / testing routes\n\n- [Section 13 - Create-Read-Update-Destroy Server Setup (2hr28min)](#section-13---create-read-update-destroy-server-setup-2hr28min)\n\n### NATS\n\n- [Section 14 - NATS Streaming Server - An Event Bus Implementation (2hr57min)](#section-14---nats-streaming-server---an-event-bus-implementation-2hr57min)\n- [Section 15 - Connecting to NATS in a Node.js World (1hr22min)](#section-15---connecting-to-nats-in-a-nodejs-world-1hr22min)\n- [Section 16 - Managing a NATS Client (1hr37min)](#section-16---managing-a-nats-client-1hr37min)\n\n### Data-replication across services\n\n- [Section 17 - Cross-Service Data Replication in Action (2hr44min)](#section-17---cross-service-data-replication-in-action-2hr44min)\n\n### Events\n\n- [Section 18 - Understanding Event Flow (30min)](#section-18---understanding-event-flow-30min)\n- [Section 19 - Listening for Events and Handling Concurrency Issues (4hr13min)](#section-19---listening-for-events-and-handling-concurrency-issues-4hr13min)\n\n### Worker services\n\n- [Section 20 - Worker Services (1hr36min)](#section-20---worker-services-1hr36min)\n\n### Payments\n\n- [Section 21 - Handling Payments (2hr40min)](#section-21---handling-payments-2hr40min)\n- [Section 22 - Back to the Client (1hr43min)](#section-22---back-to-the-client-1hr43min)\n\n### CI/CD\n\n- [Section 23 - CI/CD (2hr17min)](#section-23---cicd-2hr17min)\n\n### Docker\n\n- [Section 24 - Basics of Docker (3hr3min)](#section-24---basics-of-docker-3hr3min)\n\n### Typescript\n\n- [Section 25 - Basics of TypeScript (5hr42min)](#section-25---basics-of-typescript-5hr42min)\n\n---\n\n#### Section 1-4 summary\n\n- learnt about movement of data between services\n- sync and async communication\n- async deals with communicating changes using events sent to an event bus\n- with async - each service is self sufficient (independent of other services)\n- docker - package services\n- kubernetes -\u003e deploy + scale services\n\n## Section 01 - fundamental ideas around Microservices (46min)\n\n- each feature gets its own `service` (database)\n\n### Database-per-service\n\n- services do NOT directly access other services database (database-per-service pattern)\n  - services can run independently of other services (reliability (single point of failure): db failure -\u003e all fail, and difficult to scale (seperate db so only scale what is needed))\n  - database schema changes wont affect other services\n  - some services run better on different type of db (eg mongo vs sql)\n\n### communication strategies between services\n\n- sync\n- async\n\n#### sync\n\n- pros -\u003e service D wont need a database\n- cons -\u003e dependency between services (only as fast as slowest service)\n- cons -\u003e failure causes overall failure\n\n#### async\n\n- async communication with events (event bus handles events)\n  - method 1:\n    - services connect to event bus (single point of failure) and create/receive events (event type + data) which pass-on to/from event bus\n    - has the downsides of synchronous communication + additional problems\n  - method 2 (the way we will use it):\n    - services emit events when something happens (picked up by db of service)\n    - simultaneously an event is emitted to event bus (broadcast to anyone listening for specific event) -\u003e event picked up by service D's created database (which is combination of all required data required by service D)\n    - pros - service D will have 0 dependencies on other services\n    - pros - fast\n    - cons - duplicate data\n\n---\n\n## Section 02 - a mini microservices app (3hr35min)\n\n### 11. App overview\n\n- App: Blog post (allow users to make posts (titles) and others can add comments)\n\n- `src/blog-boilerplate` boiler plate code for Sections 2, 3, and 4\n- download:\n\n  - [Docker desktop](www.docker.com) - containerization software\n  - [Skaffold](https://skaffold.dev/) - Skaffold handles the workflow for building, pushing and deploying your application\n  - [Postman](https://www.postman.com/) - testing api\n\n- NOTE: if \\_lock files given, use that..to ensure same package versions -\u003e ie. use whatever given project files use, eg. npm, yarn or pnpm\n- NOTE: DO NOT USE THIS PROJECT AS A TEMPLATE FOR FUTURE MICROSERVICES -\u003e WILL HAVE A BETTER TEMPLATE TO USE\n- TODO: we are creating a site that allows creating posts (title) and allow comments on the post. there is comment counter\n\n- resources that will need services\n  - posts service\n  - comments service (tied to a post)\n- determine the responsibility of each service\n\n### 12. project setup\n\n- react app -\u003e src/client\n- posts -\u003e src/posts\n  - express\n  - cors\n  - axios\n  - nodemon\n- comments -\u003e src/comments\n  - express\n  - cors\n  - axios\n  - nodemon\n\n### 13. express-based project for `Posts` service\n\n- create a post (/POST) POSTMAN -\u003e body: {title:string}\n- list all posts (/GET)\n- the use of an object `posts` to store new posts\n- when creating a new post generate a random id (randomBytes) and convert to hex `.toString('hex')`\n- when user sends json data, `bodyParser` ensures data gets parsed as json\n\n```js\n//src/posts/index.js\nimport express from 'express';\nimport bodyParser from 'body-parser';\nimport { randomBytes } from 'crypto';\n\nconst app = express();\napp.use(bodyParser.json());\n\nconst posts = {};\n\napp.get('/posts', (req, res) =\u003e {\n  res.send(posts);\n});\n\napp.post('/posts', (req, res) =\u003e {\n  const id = randomBytes(4).toString('hex');\n  const { title } = req.body;\n\n  posts[id] = {\n    id,\n    title,\n  };\n\n  res.status(201).send(posts[id]); //201 - resource created\n});\n\napp.listen(4000, () =\u003e {\n  console.log('Listening on 4000');\n});\n```\n\n### 14. testing post service\n- using the code above...\n- NOTE: code will not work directly from repository as you have to comment out the eventbus code (as it is further along that this lesson)\n\n- using POSTMAN\n- POST -\u003e `http://localhost:4000/posts`\n  -\u003e body -\u003e RAW -\u003e JSON -\u003e {\"title\":string}\n  -\u003e Headers -\u003e Content-Type -\u003e application/json\n\n- GET -\u003e `http://localhost:4000/posts`\n  -\u003e Headers -\u003e Content-Type -\u003e application/json\n\n### 15. express-based project for `Comments` service\n\n- create a comment (tied to a post)\n- list all comments (comments related to a specific post)\n- Use a different port to `posts`\n- `:id/comments` -\u003e comments of an id (array of objects)\n- `req.params.id` access id param in url\n\n```js\n//src/comments/index.js\nimport express from 'express';\nimport bodyParser from 'body-parser';\nimport { randomBytes } from 'crypto';\n\nconst app = express();\napp.use(bodyParser.json());\n\nconst commentsByPostId = {};\n\napp.get('/posts/:id/comments', (req, res) =\u003e {\n  res.send(commentsByPostId[req.params.id] || []);\n});\n\napp.post('/posts/:id/comments', (req, res) =\u003e {\n  const commentId = randomBytes(4).toString('hex');\n  const { content } = req.body;\n\n  const comments = commentsByPostId[req.params.id] || [];\n\n  comments.push({ id: commentId, content });\n\n  commentsByPostId[req.params.id] = comments;\n\n  res.status(201).send(comments);\n});\n\napp.listen(4001, () =\u003e {\n  console.log('Listening on 4001');\n});\n```\n\n### 16. testing comments\n- NOTE: 123 is the id: of the post (from our created POST /posts)\n\n- POSTMAN\n- POST -\u003e `http://localhost:4001/posts/123/comments`\n  -\u003e Headers -\u003e Content-Type -\u003e application/json\n  -\u003e body -\u003e RAW -\u003e JSON -\u003e {\"content\":\"I am a comment\"}\n\n- GET -\u003e `http://localhost:4001/posts/123/comments`\n  -\u003e Headers -\u003e Content-Type -\u003e application/json\n\n### 17-25. react app\n- section01-04-blog/client/\n  - NOTE: posts/ should be running\n  - NOTE: comments/ should be running\n  - NOTE: client/ (this)\n\n- react app to use the microservices\n- App -\u003e PostCreate\n- App -\u003e PostList\n- App -\u003e PostList -\u003e CommentList\n- App -\u003e PostList -\u003e CommentCreate\n\n```js\n//src/index.js\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\u003cApp /\u003e);\n```\n\n- note: public/index.html\n- add bootstrap cdn:\n\n```html\n\u003c!-- ... --\u003e\n\n\u003clink\n  rel=\"stylesheet\"\n  href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\"\n  integrity=\"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T\"\n  crossorigin=\"anonymous\"\n/\u003e\n\n\u003c!-- ... --\u003e\n```\n\n- PostCreate -\u003e has a form with `onSubmit` that posts to localhost:4000 (posts)\n- form handler has `event.preventDefault();`\n\n```js\nawait axios.post('http://localhost:4000/posts', {\n  title,\n});\n```\n\n### 21. CORS ERRORS\n\n- cors requests errors -\u003e anytime we try access a domain/port or subdomain that is different from the url we are trying to make request to.\n- eg. localhost:3000 trying to access localhost:4000\n- localhost:4000 has to configure the server to allow cors\n\n```\nnpm i cors\n```\n\n```js\nimport cors from 'cors';\n\n//...\nconst app = express();\napp.use(bodyParser.json());\napp.use(cors());\n```\n\n### 22. fetching and rendering posts\n\n- src/client/App.js\n\n```js\n//client/src/App.js\n\nimport React from 'react';\nimport PostCreate from './PostCreate';\nimport PostList from './PostList';\n\nconst App = () =\u003e {\n  return (\n    \u003cdiv className=\"container\"\u003e\n      \u003ch1\u003eCreate Post\u003c/h1\u003e\n      \u003cPostCreate /\u003e\n      \u003chr /\u003e\n      \u003ch1\u003ePosts\u003c/h1\u003e\n      \u003cPostList /\u003e\n    \u003c/div\u003e\n  );\n};\nexport default App;\n```\n\n### 23. creating comments\n\n- client/src/CommentCreate.js\n- CommentCreate needs to know the id of the post\n\n```js\nconst CommentCreate = ({ postId }) =\u003e {};\n```\n\n### 24. displaying comments\n\n- client/src/CommentList.js\n\n```js\n//client/src/CommentList.js\n\nimport React, { useState, useEffect } from 'react';\nimport axios from 'axios';\n\nconst CommentList = ({ postId }) =\u003e {\n  const [comments, setComments] = useState([]);\n\n  const fetchData = async () =\u003e {\n    const res = await axios.get(\n      `http://localhost:4001/posts/${postId}/comments`\n    );\n\n    setComments(res.data);\n  };\n\n  useEffect(() =\u003e {\n    fetchData();\n  }, []);\n\n  const renderedComments = comments.map((comment) =\u003e {\n    return \u003cli key={comment.id}\u003e{comment.content}\u003c/li\u003e;\n  });\n\n  return \u003cul\u003e{renderedComments}\u003c/ul\u003e;\n};\n\nexport default CommentList;\n```\n\n### 26. request minimization strategies\n\n- PROBLEM -\u003e we are making one request (for posts' comments) for every post we have fetched\n- REQUIRED -\u003e a single request for post AND also all associated comments\n\n### 27. async solution\n\n- post service (emits an event when a post is created) -\u003e event broker (receives events sends them to interested parties)\n- comments service (emits an event when a comment is created) -\u003e event broker (receives events sends them to interested parties)\n- NEW: queries service (listens for when post or comment is created - assembles all blogs and comments into efficient data structure)\n\n### The Queries service\n\n- the queries service listens for new post and comment events and takes information it needs\n- CONS - is that there is data duplication\n- PROS - faster / less dependencies\n\n### 29. Event Bus overview\n\n- there are different implementations of event bus: RabbitMQ, Kafka, NATS\n- what they do? they receive events then publishes them to listeners\n- these implementations have subtle differences that make async communication easier or harder\n\n- NOTE: posts/ should be running\n- NOTE: comments/ should be running\n- NOTE: client/ should be running\n- NOTE: event-bus/ (this)\n\n- TODO: express based event bus\n  - Post service -\u003e POST /events ( localhost:4000/events)\n  - Comments service -\u003e POST /events ( localhost:4001/events)\n  - Query service -\u003e POST/events ( localhost:4002/events)\n- Event bus (port :4005) receive events from `post` OR `comments` OR `query` and send the same event to the services (\\*including the sender)\n\n### 30. node and unhandled promise rejections\n\n- NOTE: Unhandled Promise Rejections are now treated as errors instead of warnings and will cause the servers to crash.\n- FIX: you'll need to add a catch block to every request of the event-bus/index.js\n\n```js\n//POSTS: 4000\naxios.post('http://localhost:4000/events', event).catch((err) =\u003e {\n  console.log(err.message);\n});\n\n//COMMENTS: 4001\naxios.post('http://localhost:4001/events', event).catch((err) =\u003e {\n  console.log(err.message);\n});\n\n//QUERY: 4002\naxios.post('http://localhost:4002/events', event).catch((err) =\u003e {\n  console.log(err.message);\n});\n\nres.send({ status: 'OK' });\n```\n\n### 31. basic event bus implementation\n\n- event-bus/\n- as described, event bus has event handler for post /events, when it receives this (req.body), it will make calls to\n  - post /events, sending the event\n  - comments /events, sending the event\n  - query /events, sending the event\n\n### 32. emmiting events (post service)\n\n- event-bus/src/index.js is running..\n- posts/src/index.js sends event\n\n```js\n//posts/src/index.js\n\n//...\n\nawait axios.post('http://localhost:4005/events', {\n  type: 'PostCreated',\n  data: {\n    id,\n    title,\n  },\n});\n\n//...\n```\n\n### 33. emiting events (comment event)\n\n- comments/src/index.js\n\n```js\n//...\n\nawait axios.post('localhost:4005/events', {\n  type: 'CommentCreated',\n  data: {\n    id: commentId,\n    content,\n    postId: req.params.id,\n  },\n});\n```\n\n### 34. receiving events\n\n- the services need to be able to receive events (listen for events from app)\n- NOTE: if you test (client), all 4 services need to be running (comments, event-bus, posts, client)\n\n```js\n//posts/index.js\napp.post('/events', (req, res) =\u003e {\n  console.log('received event: ', req.body.type);\n\n  res.send({});\n});\n```\n\n```js\n//comments/index.js\napp.post('/events', (req, res) =\u003e {\n  console.log('received event: ', req.body.type);\n\n  res.send({});\n});\n```\n\n### 35. Data query service\n\n- /query/index.js\n- service which allows listing of post with its comments\n- query service will not emit events (no need for axios)\n- route handlers for query service:\n  - GET /posts (posts + comments)\n  - POST /events (receive events type \"CommentCreated\" and \"PostCreated\")\n\n### 36. parsing incoming events\n\n- query/index.js\n- events of type `PostCreated` and events of type `CommentCreated`:\n\n```js\nif (type === 'PostCreated') {\n  const { id, title } = data;\n  posts[id] = { id, title, comments: [] };\n}\n\nif (type === 'CommentCreated') {\n  const { id, content, postId } = data;\n  const post = posts[postId];\n  post.comments.push({ id, content });\n}\n```\n\n- example of `posts` object\n\n```js\nposts ===\n  {\n    ghgu443: {\n      id: 'ghgu443',\n      title: 'post title',\n      comments: [\n        {\n          id: 'klfjfs3',\n          content: 'comment!',\n        },\n      ],\n    },\n    ghgsad12: {\n      id: 'ghgu443',\n      title: 'post title',\n      comments: [\n        {\n          id: 'klfjfs3',\n          content: 'comment!',\n        },\n      ],\n    },\n  };\n```\n\n```js\n//query/index.js\nimport express from 'express';\nimport bodyParser from 'body-parser';\nimport cors from 'cors';\n\nconst app = express();\napp.use(bodyParser.json());\napp.use(cors());\n\nconst posts = {};\n\napp.get('/posts', (req, res) =\u003e {\n  res.send(posts);\n});\n\napp.post('/events', (req, res) =\u003e {\n  const { type, data } = req.body;\n\n  if (type === 'PostCreated') {\n    const { id, title } = data;\n\n    posts[id] = { id, title, comments: [] };\n  }\n\n  if (type === 'CommentCreated') {\n    const { id, content, postId } = data;\n\n    const post = posts[postId];\n    post.comments.push({ id, content });\n  }\n\n  console.log(posts);\n\n  res.send({});\n});\n\napp.listen(4002, () =\u003e {\n  console.log('Listening on 4002');\n});\n```\n\n### 37. using query service\n\n- TODO: client (react app) for reading data -\u003e swop out reaching to `Posts service` and `Comments service` and get data directly from `query service` :4002\n- this means client/src/PostList.js will hit the query service at port 4002\n\n```js\nconst fetchPosts = async () =\u003e {\n  const res = await axios.get('http://localhost:4002/posts');\n\n  setPosts(res.data);\n};\n```\n\n- this means CommentList wont send another query (it was depending on postId to fetch all comments of a post)\n\n```js\n\u003cCommentList comments={post.comments} /\u003e\n```\n\n### 38. simple feature\n\n- adding a feature: idea of comment moderation (filtering comments eg. with \"orange\" word)\n- comments get \"status\" property: `pending` (awaiting moderation), `approved`, `rejected`\n- assuming moderation will take some time\n\n### 39. issues with commenting filter\n\n#### Option 1 - Moderation communicates event creation to query service (CURRENT DESIGN)\n\n- user submits comment\n\n1. comment service persists the data and submits `CommentCreated` event (to event bus)\n2. event bus then sends event to all services (including moderation service)\n3. moderation service has to complete before it returns a message ( type `CommentModerated` -\u003e status (eg `approved`)) to event Bus\n4. event bus then sends ( type `CommentModerated` -\u003e status (eg `approved`)) event all services of app (including query service)\n\n- PROBLEM: the problem is moderation is not instant and can take time (eg if human required to moderate), design like this everything stops until moderation step is complete\n\n### 40. Option 2\n\n#### Option 2 - Moderation updates status at both comments AND query services\n\n1. comment service persists the data and submits `CommentCreated` event (to event bus)\n2. event bus then sends event to all services (including moderation service)\n\n- AND query service (where the event will be processed and persisted default status: `pending`) while it waits for moderation\n\n3. moderation service has to complete before it returns a message ( type `CommentModerated` -\u003e status (eg `approved`)) to event Bus\n4. event bus then sends ( type `CommentModerated` -\u003e status (eg `approved`)) event all services of app (including query service)\n5. query service updates its status to \"approved\"\n\n- PROBLEM: the query service itself shouldnt need to understand \"HOW TO\" update a service.\n- PROBLEM: because as more and more resources are added, they might have to all update data, (eg. if these other services also have to handle `comments`, then they will all need to handle all the different scenarios in the service too)\n\n### 41. Option 3 - query service only listens for update events (advised implementation method)\n\n- so now the correct way is for the moderation to happen inside the `comments service` itself. it handles all logic around a comment (how to handle and update a comment)\n- FIX: use a generic event (`CommentUpdated`) for all the various events being handled inside Comment service\n- eg. emit same event type `CommentUpdated` for all event types: CommentModerated, CommentUpvoted, CommentDownvoted, CommentPromoted, CommentAnonymized, CommentSearchable, CommentAdvertised\n- so the steps are the same as OPTION 2, BUT..\n- step 4. the comment service handles and processes all comment related events.\n- because comment service made an update to a comment a generic event `CommentUpdated` is emitted and sent to event Bus\n- event bus sends event off to query service\n- query updates itself with new prop values\n- SOLUTION: in this solution, the query service only needs to know about `CommentUpdated` event.\n\n### 42. moderation service\n\n#### CREATE: moderation service\n\n- the moderation service will watch for events\n- RECEIVE from EventBus -\u003e `CommentCreated` at `/events`\n- ... then later Moderation service needs to SEND out `CommentModerated` event.\n\n```js\n//blog/moderation/index.js\nimport axios from 'axios';\nimport express from 'express';\nimport bodyParser from 'body-parser';\nconst app = express();\napp.use(bodyParser.json());\n\napp.post('/events', (req, res) =\u003e {});\n\napp.listen(4003, () =\u003e {\n  console.log('listening on 4003');\n});\n```\n\n### 43. adding comment moderation\n\n#### UPDATE: comment service, query service\n\n- update Comment service so comments have 'status' property -\u003e `comments.push({ id: commentId, content, status: 'pending' });`\n- emit event -\u003e `CommentCreated` event -\u003e event bus\n\n```js\n//blog/comments/index.js\n//comment sending to event bus (port: 4005)\n//...\n\nawait axios.post('http://localhost:4005/events', {\n  type: 'CommentCreated',\n  data: {\n    id: commentId,\n    content,\n    postId: req.params.id,\n    status: 'pending',\n  },\n});\n```\n\n- event bus sends event to `moderation service` AND `query service`\n- the query service immediately processes (stores) the comment\n\n```js\n//blog/query/index.js\n//...\napp.post('/events', (req, res) =\u003e {\n  //...\n\n  if (type === 'CommentCreated') {\n    const { id, content, postId, status } = data; //includes 'status'\n    const post = posts[postId];\n    post.comments.push({ id, content, status });\n  }\n});\n```\n\n### 45. Handling moderation\n\n- after comment event is received by event bus, it should be sent to moderation service as well\n\n```js\n//blog/event-bus/index.js\n//...\n\napp.post('/events', (req, res) =\u003e {\n  const event = req.body;\n\n  //...\n\n  //MODERATION :4003\n  axios.post('http://localhost:4003/events', event).catch((err) =\u003e {\n    console.log(err.message);\n  });\n\n  res.send({ status: 'OK' });\n});\n\n//...\n```\n\n- the moderation service should look at `CommentCreated` event's 'content' and check (filter)\n- the moderation service should emit `CommentModerated` with status `approved` OR `rejected`\n- the event will be on req.body: `const {type, data} = req.body`\n- the moderation service will post an `CommentModerated` event to `event bus`.\n\n```js\n//blog/moderation/index.js\nimport axios from 'axios';\nimport express from 'express';\nimport bodyParser from 'body-parser';\n\nconst app = express();\napp.use(bodyParser.json());\n\napp\n  .post('/events', async (req, res) =\u003e {\n    //req.body will contain event\n    const { type, data } = req.body;\n\n    if (type === 'CommentCreated') {\n      const status = data.content.includes('orange') ? 'rejected' : 'approved';\n\n      await axios.post('https://localhost:4005/events', {\n        type: 'CommentModerated',\n        data: {\n          id: data.id,\n          postId: data.postId,\n          status,\n          content: data.content,\n        },\n      });\n    }\n    res.send({});\n  })\n  .catch((err) =\u003e {\n    console.log(err.message);\n  });\n\napp.listen(4003, () =\u003e {\n  console.log('listening on 4003');\n});\n```\n\n### 46. updating comment content\n\n- in the comment service (after event bus sends `CommentModerated` event), comment service should update `status`\n- after updating \"status\", tell other applications by emit `CommentUpdated` event (status should be `approved` or `rejected`)\n\n```js\n//blog/comments/index.js\nconst commentsByPostId = {};\n\napp.post('/events', async (req, res) =\u003e {\n  console.log('received event: ', req.body.type);\n\n  const { type, data } = req.body;\n\n  if (type === 'CommentModerated') {\n    const { postId, id, status, content } = data;\n\n    //find the comments by postId\n    const comments = commentsByPostId[postId];\n\n    //find comment in comments\n    const comment = comments.find((comment) =\u003e {\n      return comment.id === id;\n    });\n\n    //update comment status\n    comment.status = status;\n\n    //send CommentUpdated event to event bus\n    await axios.post(`http://localhost:4005`, {\n      type: 'CommentUpdated',\n      data: {\n        id,\n        status,\n        postId,\n        content,\n      },\n    });\n  }\n\n  res.send({});\n});\n\n//...\n```\n\n### 47. query service listens for CommentUpdated\n\n```js\n//blog/query/index.js\n//...\n\nif (type === 'CommentUpdated') {\n  const { id, content, postId, status } = data;\n  const post = posts[postId];\n\n  const comment = post.comments.find((comment) =\u003e {\n    return comment.id === id;\n  });\n\n  //update the actual content\n  comment.status = status;\n  comment.content = content;\n}\n\n//...\n```\n\n### 48. rendering comments by status\n\n- we update CommentList so that it renders the comments differently depending on its status\n- client/src/CommentList.js\n- if its rejected or pending show something else (not the li element)\n- NOTE: to test `pending` status, shutdown the `moderation/ service` (port:4003)\n- NOTE: if the moderation service was down while the event bus attempted to send an event to it, the event would be lost\n- FIX: deal with the missing events (see lesson 49.)\n\n```js\nimport React from 'react';\n\nconst CommentList = ({ comments }) =\u003e {\n  const renderedComments = comments.map((comment) =\u003e {\n    let content;\n\n    if (comment.status === 'approved') {\n      content = comment.content;\n    }\n\n    if (comment.status === 'pending') {\n      content = 'comment awaiting moderation';\n    }\n\n    if (comment.status === 'rejected') {\n      content = 'this comment has been rejected';\n    }\n\n    return \u003cli key={comment.id}\u003e{content}\u003c/li\u003e;\n  });\n\n  return \u003cul\u003e{renderedComments}\u003c/ul\u003e;\n};\n\nexport default CommentList;\n```\n\n### 49. missing events\n\n- if the moderation service was down when event bus submitted the event to it\n- what if the `query service` only created at a later stage? syncing?\n\n#### option 1: sync requests\n\n- syncing by requesting -\u003e code to get all older posts AND comments\n- CONS - syncronous requests...\n- CONS - need code (endpoints) to get posts and comments\n\n#### option 2: direct DB access\n\n- direct db access (query service has direct access to posts and comments db)\n- CONS - the logic to connect to db needs to be written\n\n#### option 3 (preferred method): store events (Event bus data store)\n\n- example: query service comes online later stage, it can work if it has access to past events..\n- TODO: when ANY service emits event to event bus, the event is sent to all services BUT `event Store` also stores the event\n- at a later stage, Query service can request past events from `Event bus data store` and check what the last successful event stored was and catchup missing events\n\n### 51. implementing event sync\n\n- TODO: implement option 3\n  - ie. stop query service\n  - create some posts + comments (query service sends event out -\u003e event-bus should store the event)\n  - create an end-point to retrieve all events that have occured (event data stoe)\n  - then launch query service -\u003e make sure it can get events from `event bus data store`\n  - when query service starts listening on port 4002, it should get list of all events that have been emitted from event-bus\n\n```js\n//src/event-bus/index.js\n\nconst events = [];\napp.post('/events', (req, res) =\u003e {\n  const event = req.body;\n  events.push(event);\n  //...\n});\n\napp.get('/events', (req, res) =\u003e {\n  res.send(events);\n});\n```\n\n```js\n//query/index\nimport express from 'express';\nimport bodyParser from 'body-parser';\nimport cors from 'cors';\n\nconst app = express();\napp.use(bodyParser.json());\napp.use(cors());\n\nconst posts = {};\n/*:\n\n//example of post \npost === {\n  'ghgu443': {\n    id: 'ghgu443',\n    title : \"post title\",\n    comments: [\n      {\n        id: 'klfjfs3',\n        content: \"comment!\"\n      }\n    ]\n  }\n}\n*/\n\nconst handleEvent = (type, data) =\u003e {\n  if (type === 'PostCreated') {\n    const { id, title } = data;\n    posts[id] = { id, title, comments: [] };\n  }\n\n  if (type === 'CommentCreated') {\n    const { id, content, postId, status } = data;\n    const post = posts[postId];\n    post.comments.push({ id, content, status });\n  }\n\n  if (type === 'CommentUpdated') {\n    const { id, content, postId, status } = data;\n    const post = posts[postId];\n    const comment = post.comments.find((comment) =\u003e {\n      return comment.id === id;\n    });\n    //update the actual content\n    comment.status = status;\n    comment.content = content;\n  }\n};\n\napp.get('/posts', (req, res) =\u003e {\n  res.send(posts);\n});\n\napp.post('/events', (req, res) =\u003e {\n  const { type, data } = req.body;\n  handleEvent(type, data);\n  res.send({});\n});\n\napp.listen(4002, async () =\u003e {\n  console.log('Listening on 4002');\n\n  const res = await axios.get('http://localhost:4005/events');\n  for (let event of res.data) {\n    console.log('processing event', event.type);\n    handleEvent(event.type, event.data);\n  }\n});\n```\n\n### summary\n\n- Microservices architecture involves breaking down an application into smaller, independent services that communicate over well-defined APIs.\n- This approach enhances reliability, scalability, and maintainability by allowing teams to develop, deploy, and scale services independently.\n- It also promotes data encapsulation and efficient event-driven communication, reducing the need for excessive server calls.\n\n## Section 03 - running services with docker (30min)\n\n### 53. deployment issues\n\n- WE HAVE:\n  - services (each its own app with port)\n  - each service can access each others service via `event Bus`\n  - how to deploy? rent a virtual machine (transfer apps to it)\n    - with virtual machine you can spin up addtional services (eg. comment service 1,2,3) on `load balancer` on the virtual machine\n      - CONS - new service requires additional ports which need to be added and assigned in `Event Bus`.\n    - but you can also spread the service across additional virtual machine\n      - CONS - if its on a different virtual machine, need to access its `IP address` instead of `localhost`\n- FIX: we need to find a way to keep track of all services running in application\n\n### 54. docker\n\n- NOTE: [Section 24 - Basics of Docker (3hr3min)](#section-24---basics-of-docker-3hr3min)\n- docker uses containers (isolated computing environment - contains everything required to run an app)\n- each docker container runs a service\n- additional copies of a service -\u003e spins up additional docker container\n- Docker solves the assumptions made about environment (eg. assumption npm is installed and node is installed)\n- assumption user knows startup commands to run app\n\n### 55. kubernetes\n\n- kubernetes is used to run a bunch of containers together (via configuration files)\n\n  #### cluster\n\n  - kubernetes cluster is a set of virtual machines (with varying amounts of virtual machines) AKA nodes\n  - the services in the nodes still communicate with the `Event bus`\n  - requests are handled by a `common communcation chanel` in the cluster -\u003e it figures out how and where to send events\n\n  #### Master\n\n  - a `master` - manages everything in the cluster (these virtual machines)\n\n  #### Config\n\n  - the configuration includes instructions on how to run the service -\u003e which is passed to master\n\n### 56. dont know docker?\n\n- see [Section 24 - Basics of Docker (3hr3min)](#section-24---basics-of-docker-3hr3min)\n\n### 57. notes about docker build output and buildkit\n\n- TODO: building a Docker image of our Posts service\n- the most recent versions of Docker will now have \"Buildkit\" enabled by default.\n- buildkit docker build output:\n\n```cmd\n\u003c!-- Now, with Buildkit, the final step would say: --\u003e\n\n =\u003e =\u003e exporting layers\n =\u003e =\u003e writing image sha256:ee59c34ada9890ca09145cc88ccb25d32b677fc3b61e921  0.0s\n\n```\n\n#### Disabling Buildkit to match course\n\n- Click the Docker Icon your systray\n- Select \"Preferences\"\n- Select \"Docker Engine\"\n\n```\n{\n  \"features\": {\n    \"buildkit\": false\n  },\n  \"experimental\": false\n}\n```\n\n- Apply and Restart.\n\n### 58. dockerizing the post service\n\n- blog/posts/Dockerfile\n\n#### Troubleshoot\n\nnote: it is `CMD [\"npm\", \"start\"]` NOT `CMD [\"npm\": \"start\"]`\n\n```Dockerfile\n# blog/posts/Dockerfile\nFROM node:14-alphine\n\nWORKDIR /app\nCOPY package.json ./\nRUN npm install\nCOPY ./ ./\n\nCMD [\"npm\", \"start\"]\n\n```\n\n- .gitignore -\u003e the files/folders to ignore when building an image\n- run docker eg. id of created image is `cddd85607be243e2b0dd28007520b223dc69477c2423f37663dfe3a2580a78ae`\n\n```cmd\ndocker run cddd85607be243e2b0dd28007520b223dc69477c2423f37663dfe3a2580a78ae\n```\n\n![dockerising post service](exercise_files/udemy-docker-section03-58-dockerising-post-service.png)\n\n### 59. review some basic commands\n\n![running services with docker](exercise_files/udemy-docker-section03-59-running-services-with-docker.png)\n\n- docker build -t docker-id/name-of-project .\n- docker run docker-id/name-of-project\n- docker run -it docker-id/posts sh\n- docker ps\n- docker exec -it container-id sh\n- docker logs\n\n- is going to remove all docker images that you have on your PC\n  `docker rmi $(docker images -a -q)`\n\n- deletes deployment\n  `kubectl delete deployment \u003cdeployment_name\u003e`\n\n- is going to create an image without any pre-cached stuff.\n  `docker build --no-cache -t \u003cyour_tag\u003e/posts .`\n\n- pushes to docker image storage\n  `docker push \u003cyour_tag\u003e/posts`\n\n- to recreate deployment. It is going to fetch new image from remote.\n  `kubectl apply posts-depl.yaml`\n\n### list all docker images\n\n- list all docker images: `docker images -a`\n\n### deleting image by id\n\n- `docker rmi -f \u003cimage_id\u003e`\n\n#### delete all images with tags `\u003cnone\u003e`\n\n- docker image prune\n\n-If you want to remove all images with `\u003cnone\u003e` tags in one go:\n\n- `docker rmi -f $(docker images -q -f \"dangling=true\")`\n\n### 60. dockerizing the other services\n\n- the other services in blog/ have the same node setup and command to start the service so you can copy+paste files from Post service:\n  - .dockerignore (node_modules)\n  - Dockerfile\n\n#### test\n\n```cmd\n//event-bus/\n\ndocker build -t clarklindev/event-bus .\ndocker run clarklindev/event-bus\n```\n\n## Section 04 - orchestrating collections of services with kubernetes (3hr25min)\n\n## 62. installing kubernetes\n\n- Kubernetes -\u003e tool for running a bunch of different containers\n- give kubernetes configuration to instruct it how to run AND how to interact with each other\n\n- Install option -\u003e Docker for Windows / Mac /linux\n\n  #### mac\n\n  - RECOMMENDED -\u003e macOS users should use Docker Desktops kubernetes instead of Minikube\n\n  #### Linux\n\n  - RECOMMENDED -\u003e Minikube\n\n  #### Windows -\u003e enable kubernetes\n\n  - RECOMMENDED -\u003e Windows users should use -\u003e Docker Desktop with WSL2\n  - docker toolbox icon -\u003e preferences -\u003e kubernetes -\u003e enable kubernetes -\u003e restart\n\n- NOTE: make sure Docker is running (NOT AS ADMINISTRATOR but normal user)\n- NOTE: for me, windows 11 kubernetes option from docker-desktop took a while to startup\n\n```\nkubectl version\n```\n\n```cmd out\nPS C:\\Users\\laptop\u003e kubectl version\nClient Version: v1.30.2\nKustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3\nServer Version: v1.30.2\n```\n\n- Docker-desktop -\u003e you should see 'kubernetes running' (status bar)\n\n![docker-desktop kubernetes running status](exercise_files/udemy-docker-section04-64-a-kubernetes-tour.png)\n\n### TROUBLESHOOT DOCKER KUBERNETES\n\n```\nPS C:\\Windows\\system32\u003e kubectl version\nClient Version: v1.30.2\nKustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3\nUnable to connect to the server: dial tcp [::1]:8080: connectex: No connection could be made because the target machine actively refused it.\n```\n\n#### Windows\n\n- `kubectl config get-contexts` -\u003e `kubectl config use-context docker-desktop`\n- on Windows 11 -\u003e from Q\u0026A -\u003e The only thing that worked for me was while in Docker Desktop I clicked on the \"Troubleshoot\" bug icon at the top. Then clicked the \"Clean / Purge data\" button. In the pop-up I selected all three check boxes (\"Hyper-V\", \"WSL 2\", \"Windows Containers\") and clicked the \"Delete\" button. After it completed Docker and Kubes status both went green.\n- deleted the .kube folder in my user directory\n- Delete the folder (hidden folder) `C:\\ProgramData\\DockerDesktop\\pki` OR `C:\\Users\\\u003cuser_name\u003e\\AppData\\Local\\Docker\\pki`\n\n### update .kube/config\n\n- update kube config file `c:\\Users\\\u003cuser folder\u003e\\.kube\\config` -\u003e update clusters server -\u003e `server: https://localhost:6443`\n- why this works? -\u003e When you use localhost in the server URL (e.g., server: https://localhost:6443), your system will attempt to resolve localhost using DNS resolution (starting with the local hosts file).\n\n```c:\\Users\u003cuser folder\u003e.kube\\config\n    server: https://localhost:6443\n```\n\n### update system32/drivers/etc/host\n\n- update c:/windows/system32/drivers/host\n- `c:\\Windows\\System32\\drivers\\etc\\hosts` (AS ADMINISTRATOR)\n\n```hosts\n127.0.0.1 kubernetes.docker.internal\nlocalhost kubernetes.docker.internal\n```\n\n#### Docker desktop for mac\n\n- [Q\u0026A](https://www.udemy.com/course/microservices-with-node-js-and-react/learn/lecture/19099722#questions/10956202) - By default, the server address for the cluster in the kube config file is set to `https://kubernetes.docker.internal:\u003cport number\u003e`. For Docker Desktop for Mac version 2.3.0.3 and K8s version 1.16.5, this server address needs to be changed to `https://localhost:\u003cport number\u003e`, where the port number is usually something like 6443. Once I made this change to the server address, I was finally able to connect to the local kube server and use the kubectl command without any issues.\n- kubernetes takes the docker image created and `deploys` and `manages` this instance of image (as a container) in the kubernetes cluster (node -\u003e Virtual Machine)\n- a config file for kubernetes -\u003e tells it how to manage the image and host it as container in `kubernetes cluster of nodes`\n- config also describes the access rights\n- `kubectl` is tool used to interact with the kubernetes cluster\n- kubernetes tries to find the image (described in config file) first from local computer\n- if it cant find it, then it looks in docker-hub\n\n## 63. NOTE on MiniKube\n\n- Install option -\u003e Install method with Docker-Toolbox (STATUS: unstable) or Linux (need to install [minikube](kubernetes.io/docs/tasks/tools/install-minikube))\n  - Minikube is an alternative option to using Docker Desktop's built-in Kubernetes.\n\n## 64. kubernetes tour\n\n#### terminology\n\n- NOTE: a `pod` OR `container` by definition is more or less the same thing.\n  - pod -\u003e wraps a container\n  - pod -\u003e can have multiple containers inside of it\n- Deployment -\u003e kubernetes creates a `deployment` to manage the containers (pods)\n  - it takes in the config file\n  - makes sure correct number of pods\n  - and makes sure they are running\n- Kubernetes service -\u003e gives access to running pods/containers (via easy url) -\u003e manages access of these pods (microservices) to other nodes in the kubernetes cluster\n  - ie. other services in kubernetes cluster reach out to the service instead of directly accessing other pods\n\n### 65. summary of kubernetes terminology\n\n![udemy-docker-section04-65-kubernetes-terminology.png](exercise_files/udemy-docker-section04-65-kubernetes-terminology.png)\n\n### 66. kubernetes config file\n\n- the config file kubernetes uses to create (pods, services, deployments)\n- (pods, services, deployments) are collectively known as `Objects`\n- written in YAML\n- always store these config with project source code (they are documentation)\n- NOTE: you can create `objects` without config file - BUT DONT DO THIS..\n  - unless its only for testing purposes\n\n### 67. - creating a pod\n\n- we use `apply` command to create container in kubernetes cluster\n- NOTE: later we correct this and say `DEPLOYMENTS` manage pods in kubernetes cluster\n- TODO: create a config file -\u003e create a pod that runs a container from the `Posts service`\n- NOTE: ensure Docker is running\n- TODO: rebuild the docker image of posts (with version): `docker build -t clarklindev/posts:0.0.1 .`\n\n```cmd out\n =\u003e =\u003e exporting layers 0.3s\n =\u003e =\u003e writing image sha256:1dbeadbb183283db3ceee72c451ea79f3e83d33ab384292d7b60c62e74c1b734 0.0s\n =\u003e =\u003e naming to docker.io/clarklindev/posts:0.0.1 0.0s\n```\n\n- TODO: create the directories in project folder: `infra/k8s` infrastructure\n- NOTE: `k8s` short for kubernetes\n- TODO: create a posts.yaml file (NOTE: indentation is important in YAML)\n\n```yaml\n# blog/infra/k8s/posts.yaml\n\napiVersion: v1\nkind: Pod\nmetadata:\n  name: posts\nspec:\n  containers:\n    - name: posts\n      image: clarklindev/posts:0.0.1\n      resources:\n        limits:\n          memory: '128Mi'\n          cpu: '500m'\n```\n\n- tell kubernetes to use this config file\n- go to k8s folder -\u003e tell kubernetes to use this config file:\n- note: .yaml (call apply with file name and file extension)\n\n```cmd\n//blog/infra/k8s/\nkubectl apply -f posts.yaml\n```\n\n- should get: `pod/posts created`.\n- NOTE: later we use deployments (posts-depl.yaml)\n\n### look at pods running inside cluster\n\n```cmd\nkubectl get pods\n```\n\n### 69. PODS - understanding pod specs\n\n- note: the `-` dash under `containers` means its an array (so there could be more containers added)\n- note: `name` can be anything you decide\n- NOTE: spec -\u003e `image` if you specify `:latest` or dont specify, it assumes to fetch from docker-hub, if you specify version, will first check local computer for the image.\n  ![kubernetes pod spec](exercise_files/udemy-docker-section04-69-pod-spec.png)\n\n### 70. common kubectl commands\n\n- just as Docker has its own commands, Kubectl is commands for containers\n  ![common Kubectl commands](exercise_files/udemy-docker-section04-70-common-kubectl-commands.png)\n- `kubectl apply -f posts.yaml`\n- `kubectl exec -it posts sh`\n- `kubectl delete pod posts`\n- `kubectl get pods`\n- `kubectl describe pod [podname]` -\u003e look at events inside pod\n\n### 71. kubectl alias\n\n#### Powershell\n\n- to create an alias, open powershell -\u003e `$PROFILE` -` C:\\Users\\\u003cuser\u003e\\Documents\\WindowsPowerShell\\Microsoft.PowerShell_profile.ps1`\n- create profile if it doesnt exist: `New-Item -Path $PROFILE -ItemType File -Force` -\u003e `C:\\Users\\\u003cuser\u003e\\Documents\\WindowsPowerShell\\Microsoft.PowerShell_profile.ps1`\n- Open the Profile in a Text Editor: Use Notepad (or any text editor): `notepad $PROFILE`\n- Add Your Aliases: In the opened profile file, add your Set-Alias commands. For example:\n\n```\nSet-Alias k Kubectl\n```\n\n- To apply the changes immediately without restarting PowerShell, run: `. $PROFILE`\n- Verify Your Aliases -\u003e `Get-Alias`\n\n### 72. DEPLOYMENTS\n\n- instead of creating pods directly in kubernetes cluster, we create a deployment\n- deployment -\u003e kubernetes object that manages a set of pods\n- kubernetes Deployments job is:\n\n  1. maintain the number of running pods specified\n  2. deployment takes care of pod updates:\n\n  #### update STEPS:\n\n  1. create updated pods\n  2. replace old pod instances with updated\n  3. delete old pods\n\n- you mainly use deployments by reading deployment logs\n\n### 73. creating a deployment\n\n- config file for deployment\n- //blog/infra/k8s/posts-depl.yaml (note: `depl` stands for deployment)\n- replicas -\u003e number of pods to create running a particular image.\n- NOTE: deployments have to figure out which pods it has to manage (`selector`, `metadata` help with this)\n- selector -\u003e look at all pods with label `x`\n- template-\u003emetadata -\u003e specify that pod will have label of `x`\n- spec -\u003e pod specs\n\n```yaml\n# blog/infra/k8s/posts-depl.yaml\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: posts-depl\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: posts\n  template:\n    metadata:\n      labels:\n        app: posts\n    spec:\n      containers:\n        - name: posts\n          image: clarklindev/posts:0.0.1\n\n          resources:\n            limits:\n              memory: '128Mi'\n              cpu: '500m'\n```\n\n- you can apply the posts-depl.yaml to the kubernetes cluster\n- from blog/infra/k8s/ folder:\n- create the pod...\n\n```cmd\nKubectl apply -f posts-depl.yaml\n```\n\n- expect cmd status update `deployment.apps/posts-depl created`\n\n### 74. common commands around deployments\n\n- deployment commands  \n  ![kubernetes deployment commands](exercise_files/udemy-docker-section04-74-deployment-commands.png)\n\n- `kubectl get deployments`\n- `kubectl get pods`\n- `kubectl delete pod posts-depl-7f7567b88c-fmr2q`\n\n  - if you delete a pod created by deployment, the deployment will re-create the pod\n  - after pod deleted, if you run `kubectl get pods` again, you will see a new pod with different name eg. name `posts-depl-7f7567b88c-5nkbv`\n\n  ```cmd out\n  NAME                          READY   STATUS    RESTARTS       AGE\n  posts                         1/1     Running   1 (3m3s ago)   4h18m\n  posts-depl-7f7567b88c-5nkbv   1/1     Running   0              49s\n  ```\n\n- `kubectl describe deployment [depl name]` -\u003e import part is `events`\n- `kubectl apply -f [config file name]` -\u003e deploy to kubernetes\n- `kubectl delete deployment [depl name]` -\u003e pods related to deployment are also deleted\n- `kubectl delete pod posts` -\u003e do not create pods manually/directly (if anything happens, you wont have way to start them back up)\n\n### 75. updating deployments\n\n#### METHOD 1 (PREFERED METHOD IS METHOD 2)\n\n- NOTE: Method 2 is preffered because method 1 relies on updating the version number in config\n\n#### STEPS\n\n1. make changes to project code\n2. rebuild image (tag NEW image version):\n\n```cmd\n//blog/posts/\ndocker build -t clarklindev/posts:0.0.5 .\n```\n\n3. in deployment config -\u003e update version of the image\n\n```yaml\n\u003c!-- blog/infra/k8s/posts-depl.yaml --\u003e\n\n\u003c!-- ... --\u003e\n\nspec:\n  containers:\n    - name: posts\n      image: clarklindev/posts:0.0.5\n```\n\n4. tell kubernetes to use this updated file:\n\n- `kubectl apply -f [depl file name]`\n\n  ```cmd\n  //infra/k8s/\n  kubectl apply -f posts-depl.yaml\n  kubectl get deployments\n  kubectl get pods\n  kubectl logs posts-depl-7488f87775-vph87\n\n  ```\n\n### 76. preferred deployment method\n\n#### METHOD 2\n\n- tells kubernetes to automatically get the latest version\n\n1. the deployment ALWAYS uses the `latest` tag in pod `spec` section (or image version should be omitted)\n\n```yaml\n//blog/infra/k8s\n# ...\n\n    spec:\n      containers:\n      - name: posts\n        image: clarklindev/posts:latest\n\n# ...\n```\n\n- need to apply it to kubernetes cluster to let it know to use latest image\n- blog/infra/k8s -\u003e `kubectl apply -f posts-depl.yaml`\n- `kubectl get deployments`\n\n2. make an update to your code\n\n3. rebuild the image\n\n```cmd\n//blog/posts/\ndocker build -t clarklindev/posts .\n```\n\n4. push image to docker hub\n   NOTE: you need to be logged-in on docker-desktop / docker-hub\n\n```\n//blog/posts/\ndocker push clarklindev/posts\n```\n\n```cmd\nkubectl get deployments\n```\n\n```cmd out\nNAME         READY   UP-TO-DATE   AVAILABLE   AGE\nposts-depl   1/1     1            1           9h\n```\n\n5. tell deployment to use this latest version\n\n- run command: `kubectl rollout restart deployment [depl_name]`\n\n```cmd\n//posts/\nkubectl rollout restart deployment posts-depl\nkubectl get deployments\nkubectl get pods\n\nkubectl logs posts-depl-7b87fbf7f4-clzk7\n\n```\n\n### 77. networking with services\n\n#### Access a pod with a running container through a service\n\n- Services in kubernetes is an object which is configured via config file (just like pods and deployments)\n- Services used to setup communication (event bus) between pods or communicate with a pod from outside the cluster\n\n#### 4 types of services\n\n![Kubernetes - types of services](exercise_files/udemy-docker-section04-77-types-of-services.png)\n\n1. cluster IP -\u003e (we use often) sets up an easy to remember url to access a pod (only exposes pods in the cluster)\n\n- for communication between pods in a kubernetes cluster. eg. `infra/k8s/posts-depl.yaml`, `infra/k8s/event-bus-depl.yaml`\n\n2. Node port -\u003e makes pods accessible from outside the cluster. (usually for dev purpose only) - eg. `infra/k8s/posts-srv.yaml`\n3. load balancer -\u003e (we use often) make a pod accessible from outside the cluster. (correct way to expose pod to outside world)\n4. external name -\u003e redirects an in-cluster request to CNAME url ..\n\n### 78. creating a NodePort service\n\n- Node Port -\u003e service for single pod setup\n- create config file and apply to cluster\n- label/selector of config file -\u003e is similar to html/css selector\n- nodeport service - it needs to know the apps to expose (`selector: app: posts`), and `posts-depl.yaml` has already defined this label of `posts`\n- ports are all the ports to expose (its what the apps' server port is listening on)\n\n```yaml\n# infra/k8s/posts-srv.yaml\napiVersion: v1\nkind: Service\nmetadata:\n  name: posts-srv\nspec:\n  type: NodePort\n  selector:\n    app: posts\n  ports:\n    - name: posts\n      protocol: TCP\n      port: 4000\n      targetPort: 4000\n```\n\n- targetPort vs Port:\n- NOTE: the Port and TargetPort do not have to be the same.  \n  ![udemy-docker-section04-78-nodeport-service.png](exercise_files/udemy-docker-section04-78-nodeport-service.png)\n\n### 79. Accessing NodePort services\n\n- DOCKER IS RUNNING\n- infra/k8s/posts-srv.yaml -\u003e can be applied to the kubernetes cluster\n- from infra/k8s/ directory: `kubectl apply -f posts-srv.yaml` -\u003e \"[service created]\"\n\n- TODO: list all services -\u003e `kubectl get services`\n  results:\n  `kubernetes` (default service)\n  `posts-srv`\n  ...\n  - NOTE: `posts-srv` has type `NodePort`\n  - NOTE: has port 4000:30345/TCP (the 3xxxx port is randomly assigned)\n  - NodePort -\u003e we use the 3xxxx port range to access the service from outside the cluster\n    - REMINDER: this nodePort (3xxxx) is just for development purposes\n- to see what is the node port, you can run describe command on posts-srv -\u003e `kubectl describe service posts-srv`\n\n#### access posts pod (from outside the cluster)\n\n1. get the NodePort: `kubectl describe service posts-srv`\n\n```cmd\nName:                     posts-srv\nNamespace:                default\nLabels:                   \u003cnone\u003e\nAnnotations:              \u003cnone\u003e\nSelector:                 app=posts\nType:                     NodePort\nIP Family Policy:         SingleStack\nIP Families:              IPv4\nIP:                       10.109.124.251\nIPs:                      10.109.124.251\nPort:                     posts  4000/TCP\nTargetPort:               4000/TCP\nNodePort:                 posts  30345/TCP\nEndpoints:                10.1.0.71:4000\nSession Affinity:         None\nExternal Traffic Policy:  Cluster\nEvents:                   \u003cnone\u003e\n```\n\n- NOTE: `NodePort:                 posts  30345/TCP`\n\n2.  on MAC -\u003e `Docker Toolbox with minikube` -\u003e using minikube -\u003e run `minikube ip` -\u003e gives an ip -\u003e to access service - eg. ip:[NodePort]/posts\n\nOR\n\non WIN -\u003e `Docker for windows` -\u003e use http://localhost:[NodePort]/posts\n\n- ie to access this posts pod (kubernetes cluster) via nodePort from the outside:\n- browser -\u003e `http://localhost:30345/posts`\n\n### 80. setting up Cluster IP service\n\n- GOAL of cluster IP service is to expose a pod to other pods in the kubernetes cluster\n  - each pod has/is governed by a `cluster ip service`\n- the app has `posts` which sends event to `event-bus` which then sends events to all other services\n- TODO: setup service to allow `posts` and `event-bus` to communicate with each other\n- pods in kubernetes cluster gets assigned ip address so we dont know ahead of time the ip.\n- `Posts pod` sends request to `cluster ip service` which governs access to the `Event-bus` pod\n- `Event-bus pod` sends request to `cluster ip service` which governs access to the `Posts` pod\n- tool that automates the wiring up of `cluster ip service` to pod.\n\n![posts + event-bus communication](exercise_files/udemy-docker-section04-80-posts+eventbus-communication.png)\n\n1. build image for event-bus\n2. push to docker-hub\n3. create deployment for event-bus (auto creates pod)\n4. create cluster ip service -\u003e for event-bus AND for posts\n5. wire it up\n\n### 81. build a deployment for the event bus\n\n1. build image for event-bus\n\n- from blog/event-bus/ folder:\n\n```\ndocker build -t clarklindev/event-bus .\n```\n\n2. push to docker-hub\n\n```\ndocker push clarklindev/event-bus\n```\n\n3. create a deployment for event-bus\n\n- infra/k8s/event-bus-depl.yaml\n- the config is almost identical to posts-depl.yaml\n- copy and paste posts-depl.yaml and then replace 'posts' with 'event-bus'\n\n```yaml\n# infra/k8s/event-bus-depl.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: event-bus-depl\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: event-bus\n  template:\n    metadata:\n      labels:\n        app: event-bus\n    spec:\n      containers:\n        - name: event-bus\n          image: clarklindev/event-bus:latest\n          resources:\n            limits:\n              memory: '128Mi'\n              cpu: '500m'\n```\n\n- deploy to kubernetes: goto infra/k8s/ folder -\u003e `kubectl apply -f event-bus-depl.yaml`\n- get all pods: `kubectl get pods`\n\n```cmd\nNAME                             READY   STATUS    RESTARTS       AGE\nevent-bus-depl-d8998657d-bg9tc   1/1     Running   0              27s\nposts-depl-68fd57c6c4-hlmr9      1/1     Running   1 (102m ago)   15h\n```\n\n4. create cluster ip service -\u003e for event-bus AND for posts\n\n- the pods can technically communicate with each other BUT the ip address is variable so cant know this ahead-of-time...therefore we use a `cluster ip service` to give us url\n\n### 82. Adding clusterIP services\n\n- creating a cluster ip service for each pod/container (Posts and Event-bus)\n- can create a deployment config for each service (1 config per service)\n  OR\n- PREFERRED -\u003e combine into single config\n  - put the `cluster ip service` config inside the `deployment` file its related to\n  - separate with `---`\n\n```yaml\n# blog/infra/k8s/event-bus-depl.yaml\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: event-bus-depl\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: event-bus\n  template:\n    metadata:\n      labels:\n        app: event-bus\n    spec:\n      containers:\n        - name: event-bus\n          image: clarklindev/event-bus:latest\n          resources:\n            limits:\n              memory: '128Mi'\n              cpu: '500m'\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: event-bus-srv\nspec:\n  selector:\n    app: event-bus\n  ports:\n    - name: event-bus\n      protocol: TCP\n      port: 4005\n      targetPort: 4005\n```\n\n- from infra/k8s/event-bus-depl.yaml -\u003e `kubectl apply -f event-bus-depl.yaml`\n- kubernetes picks up that ONLY event-bus-srv was added...\n\n```cmd-output\ndeployment.apps/event-bus-depl unchanged\nservice/event-bus-srv created\n```\n\n```cmd\nkubectl get services\n```\n\n- note `event-bus-srv` -\u003e type is `ClusterIP`\n\n```cmd-output\nNAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE\nevent-bus-srv   ClusterIP   10.108.175.133   \u003cnone\u003e        4005/TCP         2m10s\nkubernetes      ClusterIP   10.96.0.1        \u003cnone\u003e        443/TCP          29h\nposts-srv       NodePort    10.109.124.251   \u003cnone\u003e        4000:30345/TCP   4h24m\n```\n\n- NOTE: with the posts-depl.yaml -\u003e `posts-srv` is already used for NodePort so we use `posts-cluster-ip-srv` for the `cluster ip service`\n\n```yaml\n# blog/infra/k8s/posts-depl.yaml\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: posts-depl\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: posts\n  template:\n    metadata:\n      labels:\n        app: posts\n    spec:\n      containers:\n        - name: posts\n          image: clarklindev/posts:latest\n          resources:\n            limits:\n              memory: '128Mi'\n              cpu: '500m'\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: posts-cluster-ip-srv\nspec:\n  selector:\n    app: posts\n  ports:\n    - name: posts\n      protocol: TCP\n      port: 4000\n      targetPort: 4000\n```\n\n- from blog/infra/k8s -\u003e `kubectl apply -f posts-depl.yaml`\n\n```cmd-output\ndeployment.apps/posts-depl unchanged\nservice/posts-cluster-ip-srv created\n```\n\n### 83. how to communicate between services\n\n- from blog/infra/k8s\n\n```cmd\nkubectl get services\n```\n\n```cmd-output\nNAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE\nevent-bus-srv          ClusterIP   10.108.175.133   \u003cnone\u003e        4005/TCP         14m\nkubernetes             ClusterIP   10.96.0.1        \u003cnone\u003e        443/TCP          30h\nposts-cluster-ip-srv   ClusterIP   10.107.163.55    \u003cnone\u003e        4000/TCP         36s\nposts-srv              NodePort    10.109.124.251   \u003cnone\u003e        4000:30345/TCP   4h36m\n```\n\n#### communicate: posts TO event bus\n\n- so in posts/ project index.js after sending data to /posts, a call is made to event-bus (:4005)\n\n```js\n// /posts/index.js\n// await axios.post('http://localhost:4005/events', {\n//   type:'PostCreated',\n//   data:{\n//     id, title\n//   }\n// });\n\n// UPDATE TO\nawait axios.post('http://event-bus-srv:4005/events', {\n  type: 'PostCreated',\n  data: {\n    id,\n    title,\n  },\n});\n```\n\n- and localhost:4005 only worked when we loaded from local computer\n- when using kubernetes, this wont work -\u003e `posts/` needs to reach out and access `event-bus-srv` (cluster ip service) of `event-bus`:\n- posts to event-bus' cluster ip service of name: `event-bus-srv`\n- posts will request: `http://event-bus-srv:4005`\n\n### communicate: event-bus TO posts\n\n- event-bus to posts' cluster ip service of name: `posts-cluster-ip-srv`\n- event-bus will request: `http://posts-cluster-ip-srv:4000`\n\n### 84. updating service addresses\n\n- from blog/infra/k8s/ -\u003e `kubectl get services` to list services/ports\n- so in the projects we can now change the url when pods/cluster reach out to other pods/cluster using the service name:\n- eg. `posts` code TO `event bus` -\u003e requests should use the service -\u003e `http://event-bus-srv:4005`\n- eg. `event-bus` code TO `posts` -\u003e requests should use the service -\u003e `http://posts-cluster-ip-srv:4000`\n\n- after changes... from event-bus/ folder:\n\n  - build: `docker build -t clarklindev/event-bus . `\n  - push to docker-hub: `docker push clarklindev/event-bus`\n\n- after changes... from posts/ folder:\n\n  - build: `docker build -t clarklindev/posts .`\n  - push to docker-hub: `docker push clarklindev/posts`\n\n- `kubectl get deployments`\n\n```\nNAME             READY   UP-TO-DATE   AVAILABLE   AGE\nevent-bus-depl   1/1     1            1           4h38m\nposts-depl       1/1     1            1           29h\n```\n\n#### rollout deployments to kubernetes\n\n- tell kubernetes to redeploy\n- Deployments:\n- `kubectl rollout restart deployment event-bus-depl`\n- `kubectl rollout restart deployment posts-depl`\n\n### 85. verifying communication\n\n- `kubectl get pods`\n\n```\nNAME                             READY   STATUS    RESTARTS   AGE\nevent-bus-depl-696fbdbb6-pjt7x   1/1     Running   0          41s\nposts-depl-6cd968bbff-xvdq6      1/1     Running   0          30s\n```\n\nTODO: test with postman by making a request to `post` micro-service.  \nTODO: check `event-bus-depl-696fbdbb6-pjt7x` and `posts-depl-6cd968bbff-xvdq6` are exchanging events by looking at logs\n\n- when we do make a post, `post` should reach out to `event-bus-srv` (event-bus service) emit an event.\n- then use `kubectl logs event-bus-depl-696fbdbb6-pjt7x`\n\n- NOTE: we are trying to reach NodePort (posts-srv (for dev purpose))\n  - to get the NodePort (3xxxx port number):\n    `kubectl get services`\n\n```\nNAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE\nevent-bus-srv          ClusterIP   10.108.175.133   \u003cnone\u003e        4005/TCP         156m\nkubernetes             ClusterIP   10.96.0.1        \u003cnone\u003e        443/TCP          32h\nposts-cluster-ip-srv   ClusterIP   10.107.163.55    \u003cnone\u003e        4000/TCP         143m\nposts-srv              NodePort    10.109.124.251   \u003cnone\u003e        4000:30345/TCP   6h59m\n```\n\n- to reach `posts-srv` NodePort:\n\n#### POSTMAN\n\nin windows POSTMAN -\u003e `http://localhost:30345/posts`\n\n- headers -\u003e Content-Type -\u003e `application/json`\n- body -\u003e raw -\u003e json -\u003e `{ \"title\": \"POST\" }`\n\n- EXPECTED SERVER RESPONSE:\n- status -\u003e 201 Created\n\n```json\n{\n  \"id\": \"bf1be6ef\",\n  \"title\": \"POST\"\n}\n```\n\n- revision of docker commands:\n  \u003c!-- is going to remove all docker images that you have on your PC --\u003e\n  `docker rmi $(docker images -a -q)`\n\n\u003c!-- deletes deployment --\u003e\n\n`kubectl delete deployment \u003cdeployment_name\u003e`\n\n\u003c!-- is going to create an image without any precached stuff. --\u003e\n\n`docker build --no-cache -t \u003cyour_tag\u003e/posts .`\n\n\u003c!-- pushes to docker image storage --\u003e\n\n`docker push \u003cyour_tag\u003e/posts`\n\n\u003c!-- to recreate deployment. It is going to fetch new image from remote. --\u003e\n\n`kubectl apply posts-depl.yaml`\n\n- get pods `kubectl get pods`\n\n```\nNAME                              READY   STATUS    RESTARTS   AGE\nevent-bus-depl-785cf644c5-wjzz4   1/1     Running   0          9m51s\nposts-depl-558dbf9486-qc9sw       1/1     Running   0          9m44s\n```\n\n- TODO: vieW logs\n  - `kubectl logs posts-depl-558dbf9486-qc9sw`\n- EXPECTED -\u003e communication back from event-bus service via `posts-clusterip-srv` -\u003e received event: PostCreated\n\n```cmd\nv55\nListening on 4000\nreceived event:  PostCreated\n```\n\n### 86. adding Query, moderation, comments services\n\n- TODO (For each service - in respective project folders...):\n\n  1. - update their urls to reachout to `event-bus-srv` (infra/k8s/event-bus-depl.yaml) instead of event-bus at `localhost:4005/events`\n  2. - build image\n\n  - blog/comments/ -\u003e `docker build -t clarklindev/comments .` -\u003e `docker push clarklindev/comments`\n  - blog/moderation/ -\u003e `docker build -t clarklindev/moderation .` -\u003e `docker push clarklindev/moderation`\n  - blog/query/ -\u003e `docker build -t clarklindev/query .` -\u003e `docker push clarklindev/query`\n\n  3. - push to Docker-hub\n  4. - create a `deployment` and `cluster_ip service` for each:\n\n  - create `infra/k8s/comments-depl.yaml` \\*can copy from event-bus-depl.yaml (change reference to \"event-bus\" and update port `4001`)\n  - create `infra/k8s/query-depl.yaml` \\*can copy from event-bus-depl.yaml (change reference to \"event-bus\" and update port `4002`)\n  - create `infra/k8s/moderation-depl.yaml` \\*can copy from event-bus-depl.yaml (change reference to \"event-bus\" and update port `4003`)\n\n  - apply to cluster\n\n    - from 'infra/k8s/' folder...\n    - to apply ALL config files at once: `kubectl apply -f .`\n\n    ```cmd-out\n    deployment.apps/comments-depl created\n    service/comments-srv created\n    deployment.apps/event-bus-depl unchanged\n    service/event-bus-srv unchanged\n    deployment.apps/moderation-depl created\n    service/moderation-srv created\n    deployment.apps/posts-depl unchanged\n    service/posts-cluster-ip-srv unchanged\n    service/posts-srv unchanged\n    deployment.apps/query-depl created\n    service/query-srv created\n    ```\n\n  - `kubectl get pods` (list running containers in kubernetes cluster) -\u003e should have status of \"Running\"\n    ```cmd-out\n    NAME                               READY   STATUS    RESTARTS   AGE\n    comments-depl-6d46896699-xgrjg     1/1     Running   0          88s\n    event-bus-depl-785cf644c5-wjzz4    1/1     Running   0          80m\n    moderation-depl-5847bbbd45-8jw75   1/1     Running   0          88s\n    posts-depl-558dbf9486-qc9sw        1/1     Running   0          80m\n    query-depl-5bf4fdd49f-45cbc        1/1     Running   0          88s\n    ```\n    - if a pod is not running `kubectl describe pod \u003cpodname\u003e`\n\n  5. - update eventbus to send event to:\n       `http://localhost:4001/events` -\u003e `comments`\n       `http://localhost:4002/events` -\u003e `query`,\n       `http://localhost:4003/events` -\u003e `moderation`,\n\n  - build: `docker build -t clarklindev/event-bus .`\n  - push to docker-hub: `docker push clarklindev/event-bus`\n  - get deployments: `kubectl get deployments`\n\n  ```cmd-out\n  NAME              READY   UP-TO-DATE   AVAILABLE   AGE\n  comments-depl     1/1     1            1           40m\n  event-bus-depl    1/1     1            1           36h\n  moderation-depl   1/1     1            1           40m\n  posts-depl        1/1     1            1           36h\n  query-depl        1/1     1            1           40m\n  ```\n\n  - deploy: `kubectl rollout restart deployment event-bus-depl`\n  - check pods: `kubectl get pods`\n\n### 87. testing communication\n\n- `kubectl get services`\n\n```\nNAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE\ncomments-srv           ClusterIP   10.101.55.88     \u003cnone\u003e        4001/TCP         8m56s\nevent-bus-srv          ClusterIP   10.108.175.133   \u003cnone\u003e        4005/TCP         43h\nkubernetes             ClusterIP   10.96.0.1        \u003cnone\u003e        443/TCP          3d1h\nmoderation-srv         ClusterIP   10.102.123.71    \u003cnone\u003e        4003/TCP         8m56s\nposts-cluster-ip-srv   ClusterIP   10.107.163.55    \u003cnone\u003e        4000/TCP         43h\nposts-srv              NodePort    10.109.124.251   \u003cnone\u003e        4000:30345/TCP   47h\nquery-srv              ClusterIP   10.102.225.172   \u003cnone\u003e        4002/TCP         8m56s\n```\n\n1. make changes to code: update the urls:\n\n```js\n//blog/event-bus/index.js\n\n//...\n//POSTS: 4000\naxios.post('http://posts-cluster-ip-srv:4000/events', event).catch((err) =\u003e {\n  console.log(err.message);\n});\n\n//COMMENTS: 4001\naxios.post('http://comments-srv:4001/events', event).catch((err) =\u003e {\n  console.log(err.message);\n});\n\n//QUERY: 4002\naxios.post('http://query-srv:4002/events', event).catch((err) =\u003e {\n  console.log(err.message);\n});\n\n//MODERATION :4003\naxios.post('http://moderation-srv:4003/events', event).catch((err) =\u003e {\n  console.log(err.message);\n});\n\n//...\n```\n\n2. from blog/event-bus/ -\u003e `docker build -t clarklindev/event-bus .`\n3. push to docker-hub: `docker push clarklindev/event-bus`\n4. rollout deployment to kubernetes cluster\n\n- get deployments: `kubectl get deployments`\n- kubectl rollout restart deployment [depl_name]: `kubectl rollout restart deployment event-bus-depl`\n\n```cmd-out\ndeployment.apps/event-bus-depl restarted\n```\n\n- confirm restart:\n\n```\nNAME                              READY   STATUS    RESTARTS   AGE\ncomments-depl-7f7cb8ff45-h82sx    1/1     Running   0          5m41s\nevent-bus-depl-6dfd488465-6sd94   1/1     Running   0          58s\nmoderation-depl-dc8b4f9b7-fkqn9   1/1     Running   0          5m41s\nposts-depl-75bf6b79db-zvm98       1/1     Running   0          5m41s\nquery-depl-849cf684bf-pw8r8       1/1     Running   0          5m41s\n```\n\n- we test using POSTMAN:\n\n  - testing that `comments`, `query` and `moderation` receive events from `event-bus`\n  - test by re-making a post via NodePort to event-bus:\n\n    - POST http://localhost:NodePort/posts -\u003e headers -\u003e Content-Type: `application/json`, body: RAW json: `{\"title\" : \"POSTS\"}`\n\n    ```cmd-out\n    NAME                              READY   STATUS    RESTARTS   AGE\n    comments-depl-7f7cb8ff45-h82sx    1/1     Running   0          37m\n    event-bus-depl-7cf5c8c5b8-z9zsc   1/1     Running   0          12m\n    moderation-depl-dc8b4f9b7-fkqn9   1/1     Running   0          37m\n    posts-depl-75bf6b79db-zvm98       1/1     Running   0          37m\n    query-depl-849cf684bf-pw8r8       1/1     Running   0          37m\n    ```\n\n    - check logs (`kubectl get pods`) -\u003e `kubectl logs [name of pod]`\n      - `kubectl logs comments-depl-7f7cb8ff45-h82sx` -\u003e Listening on 4001 received event: PostCreated\n      - `kubectl logs moderation-depl-dc8b4f9b7-fkqn9` -\u003e listening on 4003\n      - `kubectl logs query-depl-849cf684bf-pw8r8` -\u003e listening on 4002 processing event: PostCreated\n\n- TROUBLESHOOT: if you are not receiving the desired output from logs,\n  - FIX: [restart deployment](https://www.udemy.com/course/microservices-with-node-js-and-react/learn/lecture/19099844#questions/11700948)\n\n```cmd\nkubectl rollout restart deployment query-depl\n```\n\n### 88. load balancer services\n\n- integrating the react application with kubernetes\n  1. put in pod (Dockerfile)\n  2. put in kubernetes cluster\n  3. purpose of react app is to initially generate html/css/js\n  - AFTER, the react-app is not relevant.. (ie. does not connect/communicate with other pods)\n- 2 options for react app to communicate with kubernetes pods\n  - OPTION 1 (DO NOT DO THIS):\n    - create a `Node Port Service` for each pod (therefore exposing the pod to outside world)\n    - WHY its bad: opening a port with each Node Port and then this needs to be updated in react\n  - OPTION 2 (PREFERED METHOD):  \n    ![option 2](exercise_files/udemy-docker-section04-88-load-balancer-services-option2.png)\n    - create a `load balancer service` (single point of entry into kubernetes cluster)\n    - the react app is going to make request to load balancer service\n    - the load balancer routes requests to appropriate pod (each pod still has a (cluster ip service))\n\n### 89. load balancer and ingress\n\n- load balancer service  \n  ![load balancer service](exercise_files/udemy-docker-section04-89-load-balancer.png)\n  - tells kubernetes cluster to reachout to its cloud provider (aws/google cloud/azure ...) and provision a `load balancer`:\n  - goal is to get traffic into single pod\n  - create a config file for loader balancer service\n  - add to cluster with `kubectl apply ...`\n  - note: load balancer is outside the cluster\n- ingress / ingress controller  \n  ![ingress controller](exercise_files/udemy-docker-section04-89-ingress-controller.png)\n  - ingress -\u003e pod with set of routing rules to distribute traffic to other services -\u003e pods\n\n### 90. Ingress Nginx Installation Info\n\n- NB: required mandatory command that needed to be run for all providers has been removed.\n- the environment-specific commands (Docker Desktop, Minikube, etc) is all that is required.\n- NOTE:\n  - install -\u003e `Ingress Nginx` / NOT `Nginx Ingress`\n\n### deleting infra/k8s\n\n- used to delete all Kubernetes resources defined in the files located in the infra/k8s/ directory.\n\n```\nkubectl delete -f infra/k8s/\n```\n\n### Docker desktop\n\n- NOTE: DOCKER IS RUNNING...\n- https://kubernetes.github.io/ingress-nginx/deploy/#quick-start\n- Copy the command from the `If you don't have Helm or if you prefer a yaml manifest) section`\n- check with `kubectl get all -n ingress-nginx` to get all in `ingress-nginx` namespace\n\n```\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.0-beta.0/deploy/static/provider/cloud/deploy.yaml\nkubectl get all -n ingress-nginx\n\n```\n\n#### troubleshoot\n\n- NOTE: if you delete kubernetes resources in infra/k8s, this command needs to be called again...\n\n```\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.0-beta.0/deploy/static/provider/cloud/deploy.yaml\n```\n\n- The YAML manifest in the command above was generated with helm template, so you will end up with almost the same resources as if you had used Helm to install the controller.\n- looking at the yaml (sourcecode), notice: it is creating an load balancer (Kind: service)\n\n```\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.0-beta.0\n  name: ingress-nginx-controller\n  namespace: ingress-nginx\nspec:\n  externalTrafficPolicy: Local\n  ipFamilies:\n  - IPv4\n  ipFamilyPolicy: SingleStack\n  ports:\n  - appProtocol: http\n    name: http\n    port: 80\n    protocol: TCP\n    targetPort: http\n  - appProtocol: https\n    name: https\n    port: 443\n    protocol: TCP\n    targetPort: https\n  selector:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n  type: LoadBalancer\n```\n\n### 91. ingress Nginx\n\n- what it does? install `load balancer service` AND `ingress`\n- Name: [ingress-nginx](https://kubernetes.github.io/ingress-nginx/) NOT `kubernetes-ingress`\n- quickstart - https://kubernetes.github.io/ingress-nginx/deploy/#quick-start\n\n### 92. Ingress v1 API Required Update + pathType Warning\n\n- https://kubernetes.io/docs/concepts/services-networking/ingress/\n- changes since beta:\n  1. A pathType needs to be added\n  2. How we specify the backend service name and port has changed\n  3. The kubernetes.io/ingress.class annotation should be removed and replaced by the ingressClassName field under the spec:\n\n```yaml\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: ingress-srv\nspec:\n  ingressClassName: nginx\n  rules:\n    - host: posts.com\n      http:\n        paths:\n          - path: /posts\n            pathType: Prefix\n            backend:\n              service:\n                name: posts-cluster-ip-srv\n                port:\n                  number: 4000\n```\n\n### TROUBLESHOOT:\n\n- WARNING: `Cannot be used with pathType Prefix Warning`\n- if you see a warning in your terminal:\n\n```\nWarning: path /posts/?{.*}/comments cannot be used with pathType Prefix\nWarning: path /?{.*} cannot be used with pathType Prefix\n```\n\n- This is not an error and only a warning and does not cause a failure of the project. If you wish to suppress the warning and follow the latest guidance, then, you can use the ImplementationSpecific pathType as explained in the updated docs:\n- https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#strict-validate-path-type\n- So, for any path that makes use of a regex, you would use `ImplementationSpecific` instead of `Prefix`.\n\neg:\n\n```yaml\n- path: /posts/?(.*)/comments\n  pathType: ImplementationSpecific\n```\n\n### 93. writing ingress config files\n\n- we created an `ingress controller` through `ingress-nginx` inside the kubernetes cluster\n- TODO: teach the controller routing rules via config file with some rules.\n- then we feed this config into the cluster which will then be discovered by `ingress controller`\n- ingress controller updates its internal set of routing rules\n- from infra/k8s/ folder:\n- note: `host: posts.com` means any incoming from posts.com\n- note: - from `path: /posts` we are sending to event to service `posts-cluster-ip-srv`\n- TODO: apply infra/k8s/ingress-srv.yaml: `kubectl apply -f ingress-srv.yaml`:\n\n```cmd-out\ningress.networking.k8s.io/ingress-srv created\n```\n\n```yaml\n//infra/k8s/ingress-srv.yaml\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: ingress-srv\nspec:\n  ingressClassName: nginx\n  rules:\n    - host: posts.com\n      http:\n        paths:\n          - path: /posts\n            pathType: Prefix\n            backend:\n              service:\n                name: posts-cluster-ip-srv\n                port:\n                  number: 4000\n\n```\n\n### 94. Important Note About Port 80\n\n- we will be editing our hosts file so that we can access posts.com/posts in our browser\n- TODO: ensure installed the ingress-nginx controller for your particular Kubernetes client.\n- TODO: identify if something is running on port 80 and shut it down\n\n#### TROUBLESHOOT - Windows\n\n- Windows Pro users, both SQL Server Reporting Services (MSSQLSERVER) and the World Wide Web Publishing Service / IIS Server have been the most common services causing a conflict.\n\n- Using Powershell with elevated permissions:\n\n```cmd\nnetstat -anb\n\n```\n\n- Scroll to the top of the returned output and find the listing for port 80.\n- If Docker is properly listening on port 80 you should see:\n\n```\nTCP   0.0.0.0:80   0.0.0.0:0   LISTENING\n```\n\n### 95. hosts file tweak\n\n- ingress-srv.yaml config: `host -\u003e posts.com` explained:\n- you can host many different domains inside a single kubernetes cluster (infrastructure for different domains hosted at single kubernetes cluster)\n- ingress-nginx assumes you can host multiple apps at different domains\n  - `host: posts.com` -\u003e is saying the config to follow... is tied to app hosted at `posts.com`\n\n```yaml\n- host: posts.com\n  http:\n    paths:\n      - path: /posts\n        pathType: Prefix\n        backend:\n          service:\n            name: posts-cluster-ip-srv\n            port:\n              number: 4000\n```\n\n#### dev environment\n\n- so in dev environment, we have to trick our app that when it tries to connect to posts.com, its actually connecting to localhost\n- host file location:\n  - win -\u003e c:\\windows\\system32\\drivers\\etc\\hosts\n  - macOS/Linux -\u003e /etc/hosts\n- AS ADMIN: open hosts -\u003e add `127.0.0.1 posts.com`\n- meaning when we try connect to posts.com, it connects to 127.0.0.1 instead and apply the ingress-nginx routing to `posts-cluster-ip-srv`\n\n```\n# Copyright (c) 1993-2009 Microsoft Corp.\n#\n# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.\n#\n# This file contains the mappings of IP addresses to host names. Each\n# entry should be kept on an individual line. The IP address should\n# be placed in the first column followed by the corresponding host name.\n# The IP address and the host name should be separated by at least one\n# space.\n#\n# Additionally, comments (such as these) may be inserted on individual\n# lines or following the machine name denoted by a '#' symbol.\n#\n# For example:\n#\n#      102.54.94.97     rhino.acme.com          # source server\n#       38.25.63.10     x.acme.com              # x client host\n\n# localhost name resolution is handled within DNS itself.\n#\t127.0.0.1       localhost\n#\t::1             localhost\n# Added by Docker Desktop\n192.168.221.177 host.docker.internal\n192.168.221.177 gateway.docker.internal\n# To allow the same kube context to work on the host and the container:\n127.0.0.1 kubernetes.docker.internal\n127.0.0.1 docker-for-desktop\n# End of section\n\n127.0.0.1 posts.com\nlocalhost posts.com\n```\n\n#### Troubleshoot\n\n- Flush the DNS Cache (Optional):\n- Open Command Prompt as Administrator (search for \"cmd,\" right-click, and select \"Run as administrator\").\n\n```cmd\nipconfig /flushdns\n```\n\n#### Troubleshoot\n\n- [posts.com/posts](https://www.udemy.com/course/microservices-with-node-js-and-react/learn/lecture/26492690#questions/11251253)\n- NOTE: The LoadBalancer service allows external access to the Ingress Controller, listening on ports 80 and 443 (https)\n- add `127.0.0.1 posts.com` in hosts file\n- fix:  ingress-srv.yaml -\u003e reference to `posts-clusterip-srv` change to `posts-cluster-ip-srv`\n- from infra/k8s/folder: `kubectl apply -f ingress-srv.yaml`\n- or delete then apply: `kubectl delete -f infra/k8s/` then  `kubectl apply -f ingress-srv.yaml`\n- if at anypoint you delete required Kubernetes resources defined in the files located in the infra/k8s/ directory, you need to recall step in lesson 90 about 'if you dont have Helm... or prefer YAML': check lesson: 90. Important - DO NOT SKIP - Ingress Nginx Installation Info\n- Copy the command from the `If you don't have Helm or if you prefer a yaml manifest) section`\n\n```\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.0-beta.0/deploy/static/provider/cloud/deploy.yaml\n```\n\n```cmd-out\nnamespace/ingress-nginx created\nserviceaccount/ingress-nginx created\nserviceaccount/ingress-nginx-admission created\nrole.rbac.authorization.k8s.io/ingress-nginx created\nrole.rbac.authorization.k8s.io/ingress-nginx-admission created\nclusterrole.rbac.authorization.k8s.io/ingress-nginx created\nclusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created\nrolebinding.rbac.authorization.k8s.io/ingress-nginx created\nrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created\nclusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created\nclusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created\nconfigmap/ingress-nginx-controller created\nservice/ingress-nginx-controller created\nservice/ingress-nginx-controller-admission created\ndeployment.apps/ingress-nginx-controller created\njob.batch/ingress-nginx-admission-create created\njob.batch/ingress-nginx-admission-patch created\ningressclass.networking.k8s.io/nginx created\nvalidatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created\n```\n\n- check with kubectl get all -n ingress-nginx to get all in `ingress-nginx` namespace\n  NOTE: something must show up when you call this...\n\nspecifically something about `ingress-nginx`\n\n![kubectl get all -n ingress-nginx](exercise_files/udemy-docker-section04-95-ingress-nginx.png)\n\n#### STEP 1:\n\n- TODO: get the 3xxxx port number\n\n```\nkubectl get services\n```\n\n#### STEP 2:\n\n- POSTMAN:\n- TODO: create a post to http://localhost:\u003c3x port\u003e/posts\n  - headers: Content-Type application/json\n  - body: RAW json { \"title\": \"POST\" }\n\n#### STEP 3:\n\n- NOTE: there is not port specified in URL\n- EXPECTED: http://posts.com/posts\n\n```\n{\n  \"c27a0eeb\": {\n    \"id\": \"c27a0eeb\",\n    \"title\": \"POST\"\n  },\n  \"03f69564\": {\n    \"id\": \"03f69564\",\n    \"title\": \"POST\"\n  }\n}\n```\n\n### 96. Important Note to Add Environment Variable\n\n- TODO: add react app to kubernetes cluster\n- React app will be running in a Docker container.\n- create-react-app currently has two bugs that prevent it from running correctly in a docker container:\n  1. [React-Scripts] v3.4.1 fails to start in Docker\n  2. websocket connection appears to be hardcoded to port 3000\n- FIX: add two environment variables to the Dockerfile in the client folder\n- add to blog/client/Dockerfile:\n\n```yaml\n# blog/client/Dockerfile\n# Add the following lines\n#...\nENV CI=true\nENV WDS_SOCKET_PORT=0\n#...\n```\n\n### 97. deploying the react app\n\n- NOTE: the updates to host file is only for development env, once deployed this is not necessary\n- the reactapp should connect to `posts.com` instead of `localhost:\u003cport\u003e`\n- NOTE: the port is also removed eg. `localhost:4000/posts` becomes `http://posts.com/posts`\n- TODO:\n\n1. create react app image\n\n```cmd\n//blog/client/\ndocker build -t clarklindev/client .\n```\n\n- push to dockerhub\n\n```cmd\ndocker push clarklindev/client\n```\n\n2. create a config for deployment\n\n- infra/k8s/client-depl.yaml\n- note: update port (create-react-app is hosted on port 3000)\n\n```yaml\n# infra/k8s/client-depl.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: client-depl\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: client\n  template:\n    metadata:\n      labels:\n        app: client\n    spec:\n      containers:\n        - name: client\n          image: clarklindev/client:latest\n          # resources:\n          #   limits:\n          #     memory: \"128Mi\"\n          #     cpu: \"500m\"\n\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: client-srv\nspec:\n  selector:\n    app: client\n  ports:\n    - name: client\n      protocol: TCP\n      port: 3000\n      targetPort: 3000\n```\n\n3. deploy to kubernetes cluster\n\n- from infra/k8s/ folder: `kubectl apply -f client-depl.yaml`\n\n```cmd-out\ndeployment.apps/client-depl created\nservice/client-srv created\n```\n\n4. make a `cluster ip service` so nginx can direct traffic to the pod (react app)\n\n### 98. unique route paths\n\n- setting up routing rules for all other microservices inside kubernetes cluster (infra/k8s/ingress-srv.yaml)\n- currently it only caters for path: /posts  \n  ![ingress controller routing](exercise_files/udemy-docker-section04-97-ingress-routing-unique-routing-paths.png)\n- NOTE: ingress-nginx cannot do routing based on method of request (GET, POST, etc) only the path.. so there is not enough information to know which pod events should reach (post or query)\n\n- FIX: update the path eg. /path/create is for POST posts in client/ and posts/\n- 1. TODO: update `blog/client/src/PostCreate.js`\n\n```js\n//blog/client/src/PostCreate.js\nawait axios.post('http://posts.com/posts/create', { title });\n```\n\n- 2. TODO: update `blog/posts/index.js`\n  - update to posts/create\n\n```js\n//blog/posts/index.js\napp.post('/posts/create', async (req, res) =\u003e {});\n```\n\n### client/ updates\n\n- TODO: from blog/client/ re-build image: `docker build -t clarklindev/client .`\n- TODO: push to docker-hub: `docker push clarklindev/client`\n- `kubectl rollout restart deployment \u003cdeployment-name\u003e`\n- deploy to kubernetes cluster: `kubectl rollout restart deployment client-depl`\n\n### posts/ updates\n\n- TODO: from blog/posts/ re-build image: `docker build -t clarklindev/posts .`\n- TODO: push to docker-hub: `docker push clarklindev/posts`\n- deploy to kubernetes cluster: `kubectl rollout restart deployment posts-depl`\n\n### 99. final route config (ingress controller)\n\n- blog/infra/ingress-srv.yaml\n- TODO: update the routing paths\n- NOTE: nginx does not support wildcard colon syntax `/posts/:id/comments` (you have to use regex)\n  - FIX: wildcard fix -\u003e `:id` becomes `?(.*)`\n- NOTE: UPDATE TO YAML STRUCTURE -\u003e `kubernetes.io/ingress.class` annotation should be removed and replaced by the ingressClassName field under the spec:\n- NOTE: `path: /?(.*)` order in array -\u003e has to be at the end (so it only tries to match paths after it has tried all other paths)\n\n```yaml\n# infra/k8s/ingress-srv.yaml\n\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: ingress-srv\n  annotations:\n    # kubernetes.io/ingress.class: nginx\n    nginx.ingress.kubernetes.io/use-regex: 'true'\nspec:\n  ingressClassName: nginx\n  rules:\n    - host: posts.com\n      http:\n        paths:\n          - path: /posts/create\n            pathType: Prefix\n            backend:\n              service:\n                name: posts-cluster-ip-srv\n                port:\n                  number: 4000\n          - path: /posts\n            pathType: Prefix\n            backend:\n              service:\n                name: query-srv\n                port:\n                  number: 4002\n          - path: /posts/?(.*)/comments\n            pathType: ImplementationSpecific\n            backend:\n              service:\n                name: comments-srv\n                port:\n                  number: 4001\n          - path: /?(.*)\n            pathType: ImplementationSpecific\n            backend:\n              service:\n                name: client-srv\n                port:\n                  number: 3000\n```\n\n- from infra/k8s/ folder re-apply the `ingress-srv.yaml`\n\n```cmd\nkubectl apply -f ingress-srv.yaml\n```\n\n```cmd-out\ningress.networking.k8s.io/ingress-srv configured\n```\n\n```cmd\nkubectl get pods\n```\n\n```cmd-out\nNAME                               READY   STATUS    RESTARTS   AGE\nclient-depl-8647d5b9d8-ddhdf       1/1     Running   0          20s\ncomments-depl-6d46896699-hz5x8     1/1     Running   0          3h55m\nevent-bus-depl-d8998657d-gnn4w     1/1     Running   0          3h55m\nmoderation-depl-5847bbbd45-q9np7   1/1     Running   0          3h55m\nposts-depl-54b6b8cb6b-jx84w        1/1     Running   0          50m\nquery-depl-5bf4fdd49f-w2nsj        1/1     Running   0          3h55m\n```\n\n- visiting: posts.com  \n  ![posts.com](exercise_files/udemy-docker-section04-99-posts.com.png)\n\n### summary\n\n- up to this point in codebase, if we want to make a change\n- have to:\n\n1. rebuild\n2. deploy to docker-hub\n3. redeploy to kubernetes\n\n- there is better way to automate updates to code inside the kubernetes cluster called `Skaffold`\n\n### 100. Skaffold introduction\n\n- Skaffold automates a lot of tasks in a kubernetes `dev` environment\n- makes it super easy to update code in a running pod\n- makes it easy to create/delete objects tied to a project at once\n\n#### install Skaffold AND add to path\n\n- [skaffold.dev](http://skaffold.dev)\n- download\n- do SHA check `certutil -hashfile skaffold-windows-amd64.exe SHA256` against: `https://github.com/GoogleContainerTools/skaffold/releases` and download the .sha256\n- rename to `skaffold.exe`\n- copy to `c:\\Program Files\\skaffold\\skaffold.exe`\n- add to system environment variables path: `c:\\Program Files\\skaffold\\`\n- open powershell\n- `skaffold` to test\n\n### 101. Skaffold API version Update\n\n- list API versions that are supported by the version of Skaffold you have installed\n\n```\nskaffold schema list\n```\n\n- upgrade skaffold config\n- This will print an updated version of your Skaffold config to the terminal so that you can copy-paste or review and update as needed\n- [skaffold fix](https://skaffold.dev/docs/references/cli/#skaffold-fix) -\u003e Update \"skaffold.yaml\" in the current folder to the latest version\n\n```\nskaffold fix\n```\n\n- The main difference between the two APIs is that the `deploy` and `kubectl` fields no longer exist:\n\n- this is the updated:\n\n```yaml\n//updated\napiVersion: skaffold/v4beta3\nkind: Config\nmanifests:\n  rawYaml:\n    - ./infra/k8s/*\n...\n```\n\n- this is the old way...\n\n```yaml\n//old method\napiVersion: skaffold/v2alpha3\nkind: Config\ndeploy:\n  kubectl:\n    manifests:\n      - ./infra/k8s/*\n```\n\n### 102. skaffold setup\n\n- we setup up skaffold with a config file\n- config tells skaffold how to manage all the projects inside cluster\n- NOTE: skaffold runs outside kubernetes cluster\n\n```\nmainfests:\n  - ./infra/k8s/*\n```\n\n- tells skaffold there is a collection of config files for kubernetes in `infra/k8s/` and it should watch the .yaml files\n- changes to any of the yaml files will cause skaffold to automatically `apply` the config to the kubernetes cluster\n  - ie. you wont have to call: `kubectl apply -f [config file.yaml]`\n- this also ensures when skaffold starts up, the config files in the manifest setting will be applied\n- ensure delete objects related to config files created by kubernetes cluster\n- `build local push:false` (see skaffold.yaml) -\u003e disable uploading to docker hub\n- `artifacts:` - is an array (each entry) -\u003e telling skaffold about project it needs to maintain\n  - specifically (in code below...) it is saying there is a pod running out of client/ directory\n  - when code changes `context: client` in client directory, skaffold will take those changes and update the pod\n  - - 2 ways to update pod:\n      1. `- src: \"src/**/*.js\"` if change in javascript file, skaffold will put it directly in the pod\n      2. other updates in client/ that dont match `src:` -\u003e scaffold will try re-build entire image eg. new package dependency added\n- NOTE: REMINDER yaml indentation is important\n- NOTE: only the react app has `src/` directory, everything else (comments, event-bus, moderation, posts, query) should match just `*.js`\n\n```yaml\n# /blog/skaffold.yaml\napiVersion: skaffold/v4beta3\nkind: Config\nmanifests:\n  rawYaml:\n    - ./infra/k8s/*\nbuild:\n  local:\n    push: false\n  artifacts:\n    - image: clarklindev/client\n      context: client\n      docker:\n        dockerfile: Dockerfile\n      sync:\n        manual:\n          - src: 'src/**/*.js'\n            dest: .\n    - image: clarklindev/comments\n      context: comments\n      docker:\n        dockerfile: Dockerfile\n      sync:\n        manual:\n          - src: '*.js'\n            dest: .\n    - image: clarklindev/event-bus\n      context: event-bus\n      docker:\n        dockerfile: Dockerfile\n      sync:\n        manual:\n          - src: '*.js'\n            dest: .\n    - image: clarklindev/moderation\n      context: moderation\n      docker:\n        dockerfile: Dockerfile\n      sync:\n        manual:\n          - src: '*.js'\n            dest: .\n    - image: clarklindev/posts\n      context: posts\n      docker:\n        dockerfile: Dockerfile\n      sync:\n        manual:\n          - src: '*.js'\n            dest: .\n    - image: clarklindev/query\n      context: query\n      docker:\n        dockerfile: Dockerfile\n      sync:\n        manual:\n          - src: '*.js'\n            dest: .\n```\n\n### 103. skaffold startup\n\n- NOTE: Docker is running\n\n#### startup skaffold\n\n- TODO: build docker images -\u003e from the project folder: blog/ (where skaffold.yaml is located)\n\n```cmd\nskaffold dev\n```\n\n#### TROUBLESHOOT\n\n- note: in the config, if you specify resources for the container... and you dont allocate enough, the build will timeout:\n\n```cmd-out\n[client] The build failed because the process exited too early. This probably means the system ran out of memory or someone called `kill -9` on the process.\n```\n\n- so either remove it or allocate more resources..\n\n```yaml\nresources:\n  limits:\n    memory: '128Mi'\n    cpu: '500m'\n```\n\n### AFTER BUILD\n\n- visit: `http://posts.com/`\n- NOTE: Reminder Create React App -\u003e picks up on changes to react project folder -\u003e rebuild app -\u003e refresh browser\n- NOTE: here all other pod apps' containers (eg. comments) -\u003e run with `nodemon ...` (which restarts project when changes occur)\n- NOTE: skaffold also redeploys when it picks up on folder updates\n- but if you dont use `nodemon` (and only use `node` to start project) and only have that skaffold re-deploys, this will not reflect changes\n\n#### NOTE:\n\n- If you're using Skaffold to manage your microservices and it detects changes in your code, it will attempt to rebuild and redeploy your application. However, if your pod doesn't use a file watcher like Nodemon (or a similar tool) to automatically restart the application when code changes occur, you won't see those changes reflected in your running pod.\n- Skaffold: Skaffold watches for changes in your local files, builds the image, and deploys it to your Kubernetes cluster.\n- Nodemon: Nodemon automatically restarts your Node.js application when it detects changes in the code.\n- NOTE: there are times that changes arent detected in containers\n\n### STOPPING SKAFFOLD\n\n- CTRL+C -\u003e stops skaffold -\u003e cleanup deployment objects\n\n### summary\n\n- skaffold automates the process of kubernetes deployments and container management\n\n---\n\n---\n\n## section 05 - architecture of multiservice apps (1hr6min)\n\n- folder: `/ticketing`\n\n### 105. big ticket items (CONS of section 1-4)\n\n- duplicate code (express server setup)\n  FIX -\u003e build a central library (npm package for common code) to share code between projects\n\n- hard to picture flow of events between services\n  FIX -\u003e define our events in shared library\n\n- hard to remember what properties events should have\n  FIX -\u003e use Typescript\n\n- hard to test event flows\n  FIX -\u003e write tests (automation)\n\n- all of kubernetes was running on computer\n  FIX -\u003e running kubernetes cluster in cloud / Skaffold workflow\n\n- event order execution\n  FIX -\u003e code for concurrency\n\n### 106. App overview\n\n- StubHub - users selling tickets to concerts/sport events to other users\n- TODO: build a ticketing app (Stubhub clone)\n  - list events\n  - click on event\n    - select date\n      - ticket price -\u003e checkout\n  - concurrency - lock ticket during intention to purchase ticket\n\n### APP FEATURES\n\n- sign up  \n\n\u003cimg\nsrc='exercise_files/udemy-docker-section05-106-screen02-sign-up.png'\nalt='udemy-docker-section05-106-screen02-sign-up.png'\nwidth=600\n/\u003e\n\n- sign in  \n\n\u003cimg\nsrc='exercise_files/udemy-docker-section05-106-screen03-sign-in.png'\nalt='udemy-docker-section05-106-screen03-sign-in.png'\nwidth=600\n/\u003e\n\n- logged-in  \n\n\u003cimg\nsrc='exercise_files/udemy-docker-section05-106-screen04-logged-in-nav.png'\nalt='udemy-docker-section05-106-screen04-logged-in-nav.png'\nwidth=600\n/\u003e\n\n- list tickets for an event  \n\n\u003cimg\nsrc='exercise_files/udemy-docker-section05-106-screen01-view-ticket-listings.png'\nalt='udemy-docker-section05-106-screen01-view-ticket-listings.png'\nwidth=600\n/\u003e\n\n- ticket details  \n\n\u003cimg\nsrc='exercise_files/udemy-docker-section05-106-screen06-ticket-detail.png'\nalt='udemy-docker-section05-106-screen06-ticket-detail.png'\nwidth=600\n/\u003e\n\n- payment  \n\n\u003cimg\nsrc='exercise_files/udemy-docker-section05-106-screen07-payment.png'\nalt='udemy-docker-section05-106-screen07-payment.png'\nwidth=600\n/\u003e\n\n- card details for payment  \n\n\u003cimg\nsrc='exercise_files/udemy-docker-section05-106-screen08-card-details-for-payment.png'\nalt='udemy-docker-section05-106-screen08-card-details-for-payment.png'\nwidth=600\n/\u003e\n\n- others can purchase this ticket\n- anyone can list/buy tickets\n- when buying a ticket (go to checkout BUT before payment)-\u003e ticket is locked (for x time) while user attempts to pay (ticket wont be shown to others )\n- failure to purchase within timeframe will unlock ticket\n- ticket seller can update ticket prices if not locked\n\n### 107. resource types\n\n- App design\n  1. User store (user , password)\n  2. ticket (title, price, userId, orderId) NOTE: userId is seller; orderId is purchase queue\n  3. Order(intent to purchase ticket) (userId is seller, status (created|cancelled|awaitingpayment|completed), ticketId, expiresAt)\n  4. Charge (orderId, status(created|failed|completed), amount, stripeId, stripeRefundId)\n\n### 108. service types\n\n- Types of services in App\n- auth (related to user authentication signup|signin|signout)\n- tickets (ticket create|edit|whether ticket can be edited)\n- orders (order creation|edit)\n- expiration (watch for orders to be created, cancels after 15min)\n- payments (handles credit card payments, cancels orders if payment fails, completes if payment succeeds - order/payment failed)\n\n### 109. Events and architecture design\n\n#### App Events\n\n- UserCreated\n- UserUpdated\n\n- OrderCreated\n- OrderCancelled\n- OrderExpirred\n\n- TicketCreated\n- TicketUpdated\n\n- ChargeCreated\n\n![diagram of services](exercise_files/udemy-docker-section05-109-architecture-design.png)\n\n- Note: the structure of this project\n\n  - client (nextjs)\n  - common\n  - auth service (mongodb)\n  - tickets service (mongodb)\n  - orders service (mongodb)\n  - payments service (mongodb)\n  - expiration service (redis)\n  - NATS Streaming sever\n\n- client uses nextjs\n- all services will use node and mongodb,\n- `expiration service` will use Redis\n- services make use of a \"common library\" npm module\n- for event-bus: NATS Streaming server\n\n### 110. notes on typescript\n\n- see [Section 25 - Basics of Typescript (5hr42min)](#section-25---basics-of-typescript-5hr42min)\n\n### 111. auth service setup\n\n![udemy-docker-section05-111-auth-service.png](exercise_files/udemy-docker-section05-111-auth-service.png)\n\n- authentication routes:\n\n  - POST -\u003e /api/users/signup\n  - POST -\u003e /api/users/signin\n  - POST -\u003e /api/users/signout\n  - GET -\u003e /api/users/currentuser\n\n- folders:\n\n  - ticketing\n    - auth\n\n- `pnpm i typescript express ts-node-dev @types/express`\n\n#### ts-node-dev\n\n- builds on top of ts-node and introduces features such as fast re-compilation (incremental compilation - meaning it only recompiles the files that have changed) and hot-reloading.\n- using npx: `npx tsc --init` -\u003e creates typescript config (without needing global typescript)\n- NOTE: a global install of typescript is required to run tsc from the terminal -\u003e `tsc --init` -\u003e creates typescript config\n\n#### running the app\n\n- using exercise_files/ticketing/auth\n- note: it is using npm (default package-lock.json). if you use pnpm, you need to convert the npm lock files so pnpm can install the correct package versions: `pnpm import`\n- then you can delete package-lock.json\n- if you run -\u003e it will complain that there is no body-parser so install it (as well as @types/body-parser):\n- `pnpm i body-parser @types/body-parser`\n- `pnpm run start` -\u003e console outputs `Listening on port 3000!`\n\n### 112. auth k8s setup (auth kubernetes setup)\n\n#### STEP 1 - build docker image\n\n- TODO: create Dockerfile -\u003e build docker image (adds to dockerhub)\n- NOTE: DOCKER IS RUNNING / internet connected\n\n##### pnpm docker command\n\n- this sets up a workdirectory of `/app`,\n- copies everything from current auth directory into that folder and adds start command\n- `.dockerignore` - ignore folder: `node_modules`\n- if you use pnpm, the Dockerfile is different to npm Dockerfile\n- pnpm requires atleast `node:18-alpine`\n- `docker build -t stephengrider/auth .` (use your own docker id)\n\n```Dockerfile\n# Use an official Node.js image\nFROM node:18-alpine\n# Install pnpm\nRUN npm ins","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclarklindev%2Fmicroservices-stephengrider-with-node-and-react","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclarklindev%2Fmicroservices-stephengrider-with-node-and-react","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclarklindev%2Fmicroservices-stephengrider-with-node-and-react/lists"}