├── LICENSE
├── README.md
├── api
└── README.md
├── cloud-native
├── README.md
└── images
│ ├── add_panel_graphana_icon.png
│ ├── build_image_nodeserver.png
│ ├── grafana_datasource.png
│ ├── grafana_home.png
│ ├── grafana_import.png
│ ├── grafana_metric.png
│ ├── grafana_monitor.png
│ ├── kab-workshop-codewind-launch-nodejs.png
│ ├── kab-workshop-codewind-new-nodejs.png
│ ├── kab-workshop-codewind-nodejs-running.png
│ ├── kab-workshop-codewind-performance-2.png
│ ├── kab-workshop-codewind-performance-test-2.png
│ ├── kab-workshop-codewind-performance-test.png
│ ├── kab-workshop-codewind-performance.png
│ ├── kab-workshop-codewind-shell-commands.png
│ ├── kab-workshop-codewind-shell.png
│ ├── kab-workshop-codewind-template-sources.png
│ ├── kab-workshop-new-project.png
│ ├── nodeserver_image.png
│ ├── podman_build_image.png
│ ├── podman_desktop_images.png
│ ├── prom_graph2.png
│ ├── prometheus_graph.png
│ └── run_nodeserver_container.png
├── conferences
├── nodeconf.eu-2021.md
├── nodeconf.eu-2022.md
└── slides
│ └── Node_js_in_the_cloud.pdf
└── helm
├── README.md
└── images
├── frontend-add-item.png
├── frontend-added-item.png
└── frontend-initial-ui.png
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NodeShift Tutorials
2 |
3 | 👋 Welcome to NodeShift Tutorials
4 |
5 | * [Cloud Native Tutorial](https://github.com/nodeshift/tutorial/blob/main/cloud-native/README.md)
6 | * This workshop provides an introduction to cloud-native development with Node.js by walking you through how to extend an Express.js-based application to leverage cloud capabilities.
7 | * **Target Audience**: This workshop is aimed at developers who are familiar with Node.js but want to gain a base understanding of some of the key concepts of cloud-native development with Node.js.
8 |
9 | * [Helm Chart Tutorial](https://github.com/nodeshift/tutorial/blob/main/helm/README.md)
10 | * This workship provide an introduction to creating a Helm Chart which can use to deploy your own Node.js application
11 | * **Target Audience**: This workshop is aimed at developers who are familiar with Kubernetes but want to gain a base understanding of deploying their app to Kubernetes via Helm.
12 |
--------------------------------------------------------------------------------
/api/README.md:
--------------------------------------------------------------------------------
1 | # Building a CRUD REST API with Node.js
2 |
3 | ## Introduction
4 |
5 | Building a microservice which provides a [REST API](https://www.redhat.com/en/topics/api/what-is-a-rest-api) is a common task as a Node.js developer. In this tutorial, we will take you through the basic steps of creating an API that implements the four base operations of persistent storage: create, read, update, and delete ([CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete)). We will do this manually so that you learn the basics of what is going on behind the scenes.
6 |
7 | When developing APIs for larger enterprise applications you will often want to use OpenAPI and an API-first approach. You can lean more about that in [Building a Node.js service using the API-first approach](https://developers.redhat.com/blog/2019/01/14/building-a-node-js-service-using-the-api-first-approach).
8 |
9 | ## Installing Express
10 |
11 | The following steps cover creating a base Express.js application. Express.js is a popular web server framework for Node.js.
12 |
13 | 1. Create a directory to host your project:
14 |
15 | ```sh
16 | mkdir node-crud-api
17 | cd node-crud-api
18 | ```
19 |
20 | 2. Initialize your project with `npm` and install the Express.js module:
21 |
22 | ```sh
23 | npm init --yes
24 | npm install express
25 | ```
26 |
27 | 3. It is important to add effective logging to your Node.js applications to facilitate observability, that is to help you understand what is happening in your application. The [NodeShift Reference Architecture for Node.js](https://github.com/nodeshift/nodejs-reference-architecture/blob/main/docs/operations/logging.md) recommends using Pino, a JSON-based logger.
28 |
29 | Installing Pino:
30 |
31 | ```sh
32 | npm install pino
33 | ```
34 |
35 | 4. Now, let's start creating our server. Create a file named `server.js`:
36 |
37 | ```sh
38 | touch server.js
39 | ```
40 |
41 | 5. Add the following to `server.js` to produce an Express.js server that responds on the `/` route with 'Hello, World!'.
42 |
43 | ```js
44 | const express = require('express');
45 | const pino = require('pino')();
46 | const app = express();
47 | const PORT = process.env.PORT || 3000;
48 |
49 | app.get('/', (req, res) => {
50 | res.send('Hello, World!');
51 | });
52 |
53 | app.listen(PORT, () => {
54 | pino.info(`Server listening on port ${PORT}`);
55 | });
56 | ```
57 |
58 | Start the application by running `node server.js` and then navigate to http://localhost:3000. You should see the server respond with 'Hello, World!'. You can stop your server by entering CTRL + C in your terminal window.
59 |
60 | ## Defining Routes
61 |
62 | A REST API provides endpoints for the basic CRUD operations. In this section we are going to define these endpoints by adding routes. CRUD operations are often mapped to HTTP operations as follows:
63 |
64 | * create -> post
65 | * read -> get
66 | * put -> update
67 | * delete -> delete
68 |
69 | We will add an implementation for each of these.
70 |
71 | Create the required CRUD API routes using the following steps:
72 |
73 | 1. Create a new file called `routes.js` and initialize a new express router:
74 |
75 | ```js
76 | const express = require('express');
77 |
78 | const router = express.Router();
79 | ```
80 |
81 | 2. Register the CRUD API routes to the router:
82 |
83 | ```js
84 | router.get('/todos', (req, res) => {
85 | res.json({
86 | message: 'Not yet implemented!'
87 | });
88 | });
89 |
90 | router.post('/todos', (req, res) => {
91 | res.json({
92 | message: 'Not yet implemented!'
93 | });
94 | });
95 |
96 | router.put('/todos/:id', (req, res) => {
97 | res.json({
98 | message: 'Not yet implemented!'
99 | });
100 | });
101 |
102 | router.delete('/todos/:id', (req, res) => {
103 | res.json({
104 | message: 'Not yet implemented!'
105 |
106 | });
107 | });
108 |
109 | router.delete('/todos', (req, res) => {
110 | res.json({
111 | message: 'Not yet implemented!'
112 | });
113 | });
114 | ```
115 |
116 | 3. Default export the created express router:
117 |
118 | ```js
119 | module.exports = router;
120 | ```
121 |
122 | 4. Install two additional npm packages needed for handling cors and parsing the request's body:
123 |
124 | ```sh
125 | npm install body-parser cors
126 | ```
127 |
128 | and then, register the packages as express middleware right before the `/` route declaration in the `server.js` file:
129 |
130 | ```js
131 | const cors = require('cors');
132 | const bodyParser = require('body-parser');
133 |
134 | ...
135 |
136 | app.use(cors());
137 | app.use(bodyParser.json());
138 | app.use(bodyParser.urlencoded({ extended: true }));
139 |
140 | ...
141 | ```
142 |
143 | Note: If you want to learn more about CORS (Cross Origin Resource Sharing) take a look at here.
144 |
145 | 5. Register the exported router as an express route right after the middleware registration in the `server.js` file:
146 |
147 | ```js
148 | const apiRoutes = require('./routes');
149 |
150 | ...
151 |
152 | app.use('/api', apiRoutes);
153 |
154 | ...
155 | ```
156 |
157 | The `server.js` should look like this:
158 |
159 | ```js
160 | const express = require('express');
161 | const pino = require('pino')();
162 | const app = express();
163 | const cors = require('cors');
164 | const bodyParser = require('body-parser');
165 |
166 | const apiRoutes = require('./routes');
167 |
168 | const PORT = process.env.PORT || 3000;
169 |
170 | app.use(cors());
171 | app.use(bodyParser.json());
172 | app.use(bodyParser.urlencoded({ extended: true }));
173 |
174 | app.use('/api', apiRoutes);
175 |
176 | app.get('/', (req, res) => {
177 | res.send('Hello, World!');
178 | });
179 |
180 | app.listen(PORT, () => {
181 | pino.info(`Server listening on port ${PORT}`);
182 | });
183 | ```
184 |
185 | Restart the server, navigate to http://localhost:3000/api/todos and you should see the server response `{ message: 'Not yet implemented!' }` presented in JSON format.
186 |
187 | ## Data persistence logic
188 |
189 | The persistence layer is most often provided by a database. For our simple example we are going to avoid adding the complexity of configuring a database and simply store items in memory. We will update each the functions mapped into each of the CRUD operations through routes so that they update the in-memory array of items.
190 |
191 | Create a persistent layer for the todos and update the express routes following these steps:
192 |
193 | 1. Create a global `todos` array inside `routes.js`:
194 |
195 | ```js
196 | let todos = []; // this is our in-memory persistence layer
197 | ```
198 |
199 | 2. Import `pino` as the logger:
200 |
201 | ```js
202 | const pino = require('pino')();
203 | ```
204 |
205 | 3. Implement the `GET /todos` route:
206 |
207 | ```js
208 | router.get('/todos', (req, res) => {
209 | res.json(todos);
210 | });
211 | ```
212 |
213 | 4. Implement the `POST /todos` route:
214 |
215 | ```js
216 | router.post('/todos', (req, res) => {
217 | const author = req.body.author;
218 | const task = req.body.task;
219 |
220 | if (!task || !author) {
221 | res.status(422).send("Unprocessable Entity");
222 | return;
223 | }
224 |
225 | const id = todos.length;
226 |
227 | const todo = {
228 | id,
229 | author: author,
230 | task: task
231 | };
232 |
233 | todos.push(todo);
234 |
235 | res.json({
236 | success: true,
237 | message: 'Todo successfully added!',
238 | });
239 | });
240 | ```
241 |
242 | 5. Implement the `PUT /todos/:id` route:
243 |
244 | ```js
245 | router.put('/todos/:id', (req, res) => {
246 | const { id } = req.params;
247 | const task = req.body.task;
248 |
249 | pino.info(`Attempting to update a todo by ID (${id})`);
250 | todos = todos.map((t) => t.id === id ? task : t);
251 |
252 | res.json({
253 | success: true,
254 | message: 'Updated todo ${id}'
255 | });
256 | });
257 | ```
258 |
259 | 6. Implement the `DELETE /todos/:id` route:
260 |
261 | ```js
262 | router.delete('/todos/:id', (req, res) => {
263 | const { id } = req.params;
264 | todos = todos.filter((t) => t.id !== id);
265 |
266 | res.json({
267 | success: true,
268 | message: 'Todo has been deleted'
269 | });
270 | });
271 | ```
272 |
273 | 7. Implement the `DELETE /todos` route:
274 |
275 | ```js
276 | router.delete('/todos', (req, res) => {
277 | todos = [];
278 |
279 | res.json({
280 | success: true,
281 | message: 'Deleted todos data'
282 | });
283 | });
284 | ```
285 |
286 | Starting the server using `node server.js` and using cURL we can test our API:
287 |
288 | 1. Create a new todo:
289 |
290 | ```sh
291 | curl -X POST http://localhost:3000/api/todos -H "Content-Type: application/json" -d '{"author": "nodeshift", "task": "Learn Node.js"}'
292 | ```
293 |
294 | 2. Get all the todos from the server:
295 |
296 | ```sh
297 | curl http://localhost:3000/api/todos
298 | ```
299 |
300 | ```
301 | [
302 | {
303 | "id": 0,
304 | "author": "nodeshift",
305 | "task": "Learn Node.js"
306 | }
307 | ]
308 | ```
309 |
310 | ## Validating inputs
311 |
312 | Any input that is passed to the API should be validated before it is used. This is important
313 | because using invalid inputs within your application could lead to failures and even worse
314 | may be crafted by an attacker to cause your application to do something unintended or return
315 | more information than you planned. A good example of this is
316 | [SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection).
317 |
318 | Try running:
319 |
320 | ```shell
321 | curl -v -X POST http://localhost:3000/api/todos -H "Content-Type: application/json" -d '{"author": "nodeshift", "tasaxk": "Learn Node.js"}'
322 |
323 | ```
324 |
325 | You'll notice that `task` is mispelled as `tasaxk` and from the output you can see that the request was rejected:
326 |
327 | ```shell
328 | < HTTP/1.1 422 Unprocessable Entity
329 | ```
330 |
331 | That is because we had added validation in the post method:
332 |
333 | ```
334 | const author = req.body.author;
335 | const task = req.body.task;
336 |
337 | if (!task || !author) {
338 | res.status(422).send("Unprocessable Entity");
339 | return;
340 | }
341 | ```
342 |
343 | This was straight forward because the input was simple. For more complex APIs you may want to use
344 | a JSON validator such as [ajv](https://www.npmjs.com/package/ajv). It is also something that using OpenAPI and API-first tools help with.
345 |
346 | ## Hardening headers with Helmet
347 |
348 | It's important to make any APIs that you expose as safe/secure as possible. In larger enterprise deployments this security may be provided by an API gateway like [Red Hat 3scale API Management](https://www.redhat.com/en/technologies/jboss-middleware/3scale). When that is not the case it's wise to use a package like Helmet to secure your Node.js application from some obvious threats. You can use Helmet to safeguard your application from usual security risks like XSS, Content Security Policy, and others.
349 |
350 | In this section we'll show you how to add helmet to your application.
351 |
352 | Enable the `helmet` express middleware by following the steps:
353 |
354 | 1. Install helmet plugin with NPM:
355 |
356 | ```sh
357 | npm install helmet
358 | ```
359 |
360 | 2. Register helmet as the **first** middleware in `server.js`:
361 |
362 | ```js
363 | const helmet = require('helmet');
364 |
365 | ...
366 |
367 | app.use(helmet());
368 | ```
369 |
370 | By using the `helmet()` middleware like this we are enabling the following helmet options:
371 |
372 | - contentSecurityPolicy
373 | - dnsPrefetchControl
374 | - expectCt
375 | - frameguard
376 | - hidePoweredBy
377 | - hsts
378 | - ieNoOpen
379 | - noSniff
380 | - permittedCrossDomainPolicies
381 | - referrerPolicy
382 | - xssFilter
383 |
384 | At this point you have a REST API which supports the basic CRUD operations and is protected from some of typical threats.
385 |
386 | ## Wrap up and next steps
387 |
388 | This tutorial provided you with a basic introduction to creating a REST API that implements the four CRUD operations and you should understand the underlying concepts.
389 |
390 | If you have not already read some of the references we provided along the way now would be a great time to either go back in order to get a deeper level of understanding of some of the concepts or note them down to check out later.
391 |
--------------------------------------------------------------------------------
/cloud-native/README.md:
--------------------------------------------------------------------------------
1 | # Node.js in the Cloud
2 |
3 | Welcome :wave: to the Node.js in the Cloud workshop!
4 |
5 | The workshop provides an introduction to cloud-native development with Node.js by walking you through how to extend an Express.js-based application to leverage cloud capabilities.
6 |
7 | **Target Audience:** This workshop is aimed at developers who are familiar with Node.js but want to gain a base understanding of some of the key concepts of cloud-native development with Node.js.
8 |
9 | ## Extending an application to leverage cloud capabilities
10 |
11 | ### Building a Cloud-Ready Express.js Application
12 |
13 | This will show you how to take a Node.js application and make it "cloud-ready" by adding support for Cloud Native Computing Foundation (CNCF) technologies.
14 |
15 | In this self-paced tutorial you will:
16 |
17 | - Create an Express.js application
18 | - Add logging, metrics, and health checks
19 | - Build your application with Podman
20 | - Package your application with Helm
21 | - Deploy your application to Kubernetes
22 | - Monitor your application using Prometheus
23 |
24 | The application you'll use is a simple Express.js application. You'll learn about Health Checks, Metrics, Podman, Kubernetes, Prometheus, and Grafana. In the end, you'll have a fully functioning application running as a cluster in Kubernetes, with production monitoring.
25 |
26 | The content of this tutorial is based on recommendations from the [NodeShift Reference Architecture for Node.js](https://github.com/nodeshift/nodejs-reference-architecture).
27 |
28 | ## Prerequisites
29 |
30 | Before getting started, make sure you have the following prerequisites installed on your system.
31 |
32 | 1. Install [Node.js 16](https://nodejs.org/en/download/) (or use [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) for Linux, macOS or [nvm-windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) for Windows)
33 | 1. Podman v4 (and above)
34 | - **On Mac**: [Podman](https://podman.io/getting-started/installation#macos)
35 | - **On Windows**: Skip this step, as for installing Podman you will get a prompt during Podman Desktop installation.
36 | - **On Linux**: [Podman](https://podman.io/getting-started/installation#installing-on-linux)
37 | 1. Podman Desktop
38 | - **On Mac**: [Podman Desktop](https://podman-desktop.io/downloads/macOS)
39 | - **On Windows**: [Podman Desktop](https://podman-desktop.io/downloads/windows)
40 | - **On Linux**: [Podman Desktop](https://podman-desktop.io/downloads/linux)
41 | 1. Kubernetes
42 | - **On Mac**: [minikube](https://minikube.sigs.k8s.io/docs/start/)
43 | - **On Windows**: [minikube](https://minikube.sigs.k8s.io/docs/start/)
44 | - **On Linux**: [minikube](https://minikube.sigs.k8s.io/docs/start/)
45 | 1. Helm v3 - [Installation](./README.md#installing-helm-v37)
46 | - **Note**: This workshop tested with Helm v3.7
47 |
48 | ## Setting up
49 |
50 | ### Starting Podman Machine
51 |
52 | #### Linux
53 |
54 | Nothing to do, no Podman machine is required on Linux
55 |
56 | #### On Mac
57 |
58 | After installing Podman, open a terminal and run the below commands to initialize and start the Podman machine:
59 |
60 | _**NOTE:** \*On Apple M1 Pro chip, the system version has to be 12.4 and above._
61 |
62 | ```
63 | podman machine init --cpus 2 --memory 8096 --disk-size 20
64 | podman machine start
65 | podman system connection default podman-machine-default-root
66 | ```
67 |
68 | #### On Windows
69 |
70 | 1. Launch Podman Desktop and on the home tab click on **install podman**. In case of any missing parts for podman installation (e.g. WSL, hyper-v, etc.) follow the instructions indicated by Podman Desktop on the home page. In that case, you might need to reboot your system several times.
71 |
72 | 1. After installing podman, set WSL2 as your default WSL by entering the below command in PowerShell (with administration privileges).
73 |
74 | ```
75 | wsl --set-default-version 2
76 | ```
77 |
78 | 1. On Podman Desktop Home tab -> click on **initialize Podman** -> wait till the initialization is finished
79 | 1. On Podman Desktop Home tab -> click on **Run Podman** to run podman.
80 |
81 | #### On Windows **Home**
82 |
83 | 1. Download Podman from https://github.com/containers/podman/releases the Windows installer file is named podman-v.#.#.#.msi
84 | 1. Run the MSI file
85 | 1. Launch as Administrator a new Command Prompt
86 | 1. On the Command Prompt run:
87 | ```
88 | podman machine init
89 | podman machine set --rootful
90 | podman machine start
91 | ```
92 | 1. Launch Podman Desktop to see and manage your containers, images, etc.
93 |
94 | ### Starting Kubernetes
95 |
96 | #### On Mac:
97 |
98 | 1. Install Minikube
99 |
100 |
101 | Download binary file (click to expand)
102 |
103 | **x86-64**
104 | ```
105 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64
106 | ```
107 |
108 | **ARM64**
109 |
110 | ```
111 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-arm64
112 | ```
113 |
114 |
115 |
116 | Add minikube binary file to your `PATH system variable`
117 |
118 | ```
119 | chmod +x minikube-darwin-*
120 | mv minikube-darwin-* /usr/local/bin/minikube
121 | ```
122 |
123 | 1. start minikube
124 | ```
125 | minikube start --driver=podman --container-runtime=cri-o
126 | ```
127 |
128 | #### On Windows:
129 |
130 | 1. Download minikube
131 | ```
132 | https://github.com/kubernetes/minikube/releases/latest/download/minikube-windows-amd64.exe
133 | ```
134 | 1. Rename `minikube-windows-amd64.exe` to `minikube.exe`
135 | 1. Move minikube under `C:\Program Files\minikube` directory
136 |
137 | 1. Add `minikube.exe` binary to your `PATH system variable`
138 |
139 | 1. Right-click on the **Start Button** -> Select **System** from the context menu -> click on **Advanced system settings**
140 | 1. Go to the **Advanced** tab -> click on **Environment Variables** -> click the variable called **Path** -> **Edit**
141 | 1. Click **New** -> Enter the path to the folder containing the binary e.x. `C:\Program Files\minikube` -> click **OK** to save the changes to your variables
142 | 1. Start Podman Desktop and click on run podman
143 |
144 | 1. Start minikube:
145 |
146 | - For windows Start minikube by opening Powershell or Command Prompt **as administrator** and enter the following command:
147 |
148 | ```
149 | minikube start
150 | ```
151 |
152 | - For windows **Home** Start minikube by opening Powershell or Command Prompt **as administrator** and enter below command.
153 |
154 | ```
155 | minikube start --driver=podman --container-runtime=containerd
156 | ```
157 |
158 | #### On Linux
159 |
160 | 1. Install Minikube
161 |
162 |
163 | Download binary file (click to expand)
164 |
165 | **x86-64**
166 | ```
167 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
168 | ```
169 |
170 | **ARM64**
171 |
172 | ```
173 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm64
174 | ```
175 |
176 | **ARMv7**
177 |
178 | ```
179 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm
180 | ```
181 |
182 | **ppc64**
183 |
184 | ```
185 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-ppc64le
186 | ```
187 |
188 | **S390x**
189 |
190 | ```
191 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-s390x
192 | ```
193 |
194 |
195 |
196 | Add minikube binary file to your `PATH system variable`
197 |
198 | ```
199 | chmod +x minikube-linux-*
200 | mv minkube-linux-* /usr/local/bin/minikube
201 | ```
202 |
203 | 1. Change minikube for starting podman rootless
204 | https://minikube.sigs.k8s.io/docs/drivers/podman/#rootless-podman
205 |
206 | ```
207 | minikube config set rootless true
208 | ```
209 |
210 | 1. start minikube
211 | ```
212 | minikube start --driver=podman --container-runtime=containerd
213 | ```
214 | 2. Possible additional steps needed:
215 | * Delegation also needed on Ubuntu 2022 - https://rootlesscontaine.rs/getting-started/common/cgroup2/
216 |
217 | ### Installing Helm v3.7
218 |
219 | Helm is a package manager for Kubernetes. By installing a Helm "chart" into your Kubernetes cluster you can quickly run all kinds of different applications. You can install Helm by downloading the binary file and adding it to your PATH:
220 |
221 | 1. Download the binary file from the section **Installation and Upgrading** for your operating system.
222 |
223 | - https://github.com/helm/helm/releases/tag/v3.7.2
224 |
225 | 1. Extract it:
226 |
227 | - On Linux:
228 | ```
229 | tar -zxvf helm-v3.7.2-*
230 | ```
231 | - On Windows: **Right Click** on `helm-v3.7.2-windows-amd64` zipped file -> **Extract All** -> **Extract**
232 | - On Mac:
233 | ```
234 | tar -zxvf helm-v3.7.2-*
235 | ```
236 |
237 | 1. Add helm binary file to your `PATH system variable`
238 |
239 | On Linux and Mac (`sudo` required for `cp` step on Linux):
240 |
241 | ```
242 | cp `.//helm` /usr/local/bin/helm
243 | rm -rf ./
244 | ```
245 |
246 | If running on macOS, a pop-up indicating that the application could not be verified may appear. You will need to go to Apple menu > System Preferences, click `Security & Privacy` and allow `helm` to run.
247 |
248 | On Windows:
249 |
250 | 1. Move helm binary file to `C:\Program Files\helm`
251 | 1. Right-click on the **Start Button** -> Select **System** from the context menu -> click on **Advanced system settings**
252 | 1. Go to the **Advanced** tab -> click on **Environment Variables** -> click the variable called **Path** -> **Edit**
253 | 1. Click **New** -> Enter the path to the folder containing the binary e.x. `C:\Program Files\helm` -> click **OK** to save the changes to your variables
254 |
255 | ### 1. Create your Express.js Application
256 |
257 | The following steps cover creating a base Express.js application. Express.js is a popular web server framework for Node.js.
258 |
259 | 1. Create a directory to host your project:
260 |
261 | ```sh
262 | mkdir nodeserver
263 | cd nodeserver
264 | ```
265 |
266 | 1. Initialize your project with `npm` and install the Express.js module:
267 |
268 | ```sh
269 | npm init --yes
270 | npm install express
271 | ```
272 |
273 | 1. We'll also install the Helmet module. Helmet is a middleware that we can use to set some sensible default headers on our HTTP requests.
274 |
275 | ```sh
276 | npm install helmet
277 | ```
278 |
279 | 1. It is important to add effective logging to your Node.js applications to facilitate observability, that is to help you understand what is happening in your application. The [NodeShift Reference Architecture for Node.js](https://github.com/nodeshift/nodejs-reference-architecture/blob/main/docs/operations/logging.md) recommends using Pino, a JSON-based logger.
280 |
281 | Install Pino:
282 |
283 | ```sh
284 | npm install pino
285 | ```
286 |
287 | 1. Now, let's start creating our server. Create a file named `server.js`
288 |
289 | 1. Add the following to `server.js` to produce an Express.js server that responds on the `/` route with 'Hello, World!'.
290 |
291 | ```js
292 | const express = require('express');
293 | const helmet = require('helmet');
294 | const pino = require('pino')();
295 | const PORT = process.env.PORT || 3000;
296 |
297 | const app = express();
298 |
299 | app.use(helmet());
300 |
301 | app.get('/', (req, res) => {
302 | res.send('Hello, World!');
303 | });
304 |
305 | app.listen(PORT, () => {
306 | pino.info(`Server listening on port ${PORT}`);
307 | });
308 | ```
309 |
310 | 1. Start your application:
311 |
312 | ```sh
313 | npm start
314 |
315 | > nodeserver@1.0.0 start /private/tmp/nodeserver
316 | > node server.js
317 |
318 | {"level":30,"time":1622040801251,"pid":21934,"hostname":"bgriggs-mac","msg":"Server listening on port 3000"}
319 | ```
320 |
321 | Navigate to [http://localhost:3000](http://localhost:3000) and you should see the server respond with 'Hello, World!'. You can stop your server by entering `CTRL + C` in your terminal window.
322 |
323 | ### 2. Add Health Checks to your Application
324 |
325 | Kubernetes, and a number of other cloud deployment technologies, provide "Health Checking" as a system that allows the cloud deployment technology to monitor the deployed application and to take action should the application fail or report itself as "unhealthy".
326 |
327 | The simplest form of Health Check is process-level health checking, where Kubernetes checks to see if the application process still exists and restarts the container (and therefore the application process) if it is not. This provides a basic restart capability but does not handle scenarios where the application exists but is unresponsive, or where it would be desirable to restart the application for other reasons.a
328 |
329 | The next level of Health Check is HTTP-based, where the application exposes a "livenessProbe" URL endpoint that Kubernetes can make requests to determine whether the application is running and responsive. Additionally, the request can be used to drive self-checking capabilities in the application.
330 |
331 | Add a Health Check endpoint to your Express.js application using the following steps:
332 |
333 | 1. Register a Liveness endpoint in `server.js`:
334 |
335 | ```js
336 | app.get('/live', (req, res) => res.status(200).json({ status: 'ok' }));
337 | ```
338 |
339 | Add this line after the `app.use(helmet());` line. This adds a `/live` endpoint to your application. As no liveness checks are registered, it will return a status code of 200 OK and a JSON payload of `{"status":"ok"}`.
340 |
341 | 2. Restart your application:
342 |
343 | ```sh
344 | npm start
345 | ```
346 |
347 | 3. Check that your `livenessProbe` Health Check endpoint is running. Visit the `live` endpoint [http://localhost:3000/live](http://localhost:3000/live).
348 |
349 | For information more information on health/liveness checks, refer to the following:
350 | - [NodeShift Reference Architecture for Node.js Applications - Health Checks](https://github.com/nodeshift/nodejs-reference-architecture/blob/master/docs/operations/healthchecks.md)
351 | - [Red Hat Developer Blog on Health Checking](https://developers.redhat.com/blog/2020/11/10/you-probably-need-liveness-and-readiness-probes/?sc_cid=7013a0000026DqpAAE)
352 |
353 | ### 3. Add Metrics to your Application
354 |
355 | For any application deployed to a cloud, it is important that the application is "observable": that you have sufficient information about an application and its dependencies such that it is possible to discover, understand and diagnose the state of the application. One important aspect of application observability is metrics-based monitoring data for the application.
356 |
357 | One of the CNCF recommended metrics systems is [Prometheus](http://prometheus.io), which works by collecting metrics data by making requests of a URL endpoint provided by the application. Prometheus is widely supported inside Kubernetes, meaning that Prometheus also collects data from Kubernetes itself, and application data provided to Prometheus can also be used to automatically scale your application.
358 |
359 | The `prom-client` package provides a library that auto-instruments your application to collect metrics. It is then possible to expose the metrics on an endpoint for consumption by Prometheus.
360 |
361 | Add a `/metrics` Prometheus endpoint to your Express.js application using the following steps:
362 |
363 | 1. Add the `prom-client` dependency to your project:
364 |
365 | ```sh
366 | npm install prom-client
367 | ```
368 |
369 | 2. Require `prom-client` in `server.js` and choose to configure default metrics:
370 |
371 | ```js
372 | // Prometheus client setup
373 | const Prometheus = require('prom-client');
374 | Prometheus.collectDefaultMetrics();
375 | ```
376 |
377 | It is recommended to add these lines around Line 3 below the `pino` logger import.
378 |
379 | 3. Register a `/metrics` route to serve the data on:
380 |
381 | ```js
382 | app.get('/metrics', async (req, res, next) => {
383 | try {
384 | res.set('Content-Type', Prometheus.register.contentType);
385 | const metrics = await Prometheus.register.metrics();
386 | res.end(metrics);
387 | } catch {
388 | res.end('');
389 | }
390 | });
391 | ```
392 |
393 | Register the `app.get('/metrics')...` route after your `/live` route handler. This adds a `/metrics` endpoint to your application. This automatically starts collecting data from your application and exposes it in a format that Prometheus understands.
394 |
395 | Check that your metrics endpoint is running:
396 |
397 | 1. Start your application:
398 |
399 | ```sh
400 | npm start
401 | ```
402 |
403 | 2. Visit the `metrics` endpoint [http://localhost:3000/metrics](http://localhost:3000/metrics).
404 |
405 | For information on how to configure the `prom-client` library see the [prom-client documentation](https://github.com/siimon/prom-client#prometheus-client-for-nodejs---).
406 |
407 | You can install a local Prometheus server to graph and visualize the data, and additionally to set up alerts. For this workshop, you'll use Prometheus once you've deployed your application to Kubernetes.
408 |
409 | ### 4. Building your Application with Podman
410 |
411 | Before you can deploy your application to Kubernetes, you first need to build your application into a container and produce a container image. This packages your application along with all of its dependencies in a ready-to-run format.
412 |
413 | NodeShift provides a "[Docker](https://github.com/NodeShift/docker)" project that provides several Dockerfile templates that can be used to build your container and produce your image. The same file format can be used with Podman.
414 |
415 | For this workshop, you'll use the `Dockerfile-run` template, which builds a production-ready Docker image for your application.
416 |
417 | Build a production Docker image for your Express.js application using the following steps:
418 |
419 | #### On Mac/Linux
420 | 1. Copy the `Dockerfile-run` template into the root of your project:
421 |
422 | ```sh
423 | curl -fsSL -o Dockerfile-run https://raw.githubusercontent.com/NodeShift/docker/master/Dockerfile-run
424 | ```
425 |
426 | 2. Also, copy the `.dockerignore` file into the root of your project:
427 |
428 | ```sh
429 | curl -fsSL -o .dockerignore https://raw.githubusercontent.com/NodeShift/docker/master/.dockerignore
430 | ```
431 |
432 | 3. Build the Docker run image for your application:
433 |
434 | ```sh
435 | podman build --tag nodeserver:1.0.0 --file Dockerfile-run .
436 | ```
437 |
438 | You have now built a container image for your application called `nodeserver` with a version of `1.0.0`. Use the following to run your application inside the container:
439 |
440 | ```sh
441 | podman run --interactive --publish 3000:3000 --tty nodeserver:1.0.0
442 | ```
443 |
444 | This runs your container image in a Podman container, mapping port 3000 from the container to port 3000 on your laptop so that you can access the application.
445 |
446 | #### On Windows
447 |
448 | We will use Podman Desktop to build and run our image.
449 | 1. Create a file called `Dockerfile-run` and paste the content from the below URL: https://raw.githubusercontent.com/NodeShift/docker/master/Dockerfile-run
450 |
451 | 1. Create another file called `.dockerignore` and paste the content from the below URL: https://raw.githubusercontent.com/NodeShift/docker/master/.dockerignore
452 |
453 | 1. Run Podman Desktop
454 |
455 | 1. On Podman Desktop Click on Images (tab - left sidebar) -> Build Image
456 |
457 |
458 | Available images (click to expand)
459 |
460 | 
461 |
462 |
463 | 1. Set the `containerfile` path which in our case points to the `Dockerfile-run` and the image name `nodeserver` -> Build
464 |
465 |
466 | Start building image (click to expand)
467 |
468 | 
469 |
470 |
471 |
472 | Image build process (click to expand)
473 |
474 | 
475 |
476 |
477 | 1. After the build process, on the `Images` tab the `nodeserver` Image should be visible.
478 |
479 |
480 | Final build image (click to expand)
481 |
482 | 
483 |
484 |
485 | 1. Hover over the container image -> click on the play button to run the image -> set ports 3000 and 8080 -> start container
486 |
487 |
488 | Run image on port 3000 (click to expand)
489 |
490 | 
491 |
492 | Visit your application's endpoints to check that it is running successfully:
493 |
494 | * Homepage: [http://localhost:3000/](http://localhost:3000/)
495 | * Liveness: [http://localhost:3000/health](http://localhost:3000/live)
496 | * Metrics: [http://localhost:3000/metrics](http://localhost:3000/metrics)
497 |
498 | ## 5. Packaging your Application with Helm
499 |
500 | To deploy your container image to Kubernetes you need to supply Kubernetes with configuration on how you need your application to be run, including which container image to use, how many replicas (instances) to deploy, and how much memory and CPU to provide to each.
501 |
502 | Helm charts provide an easy way to package your application with this information.
503 |
504 | NodeShift provides a "[Helm](https://github.com/NodeShift/helm)" project that provides a template Helm chart template that can be used to package your application for Kubernetes.
505 |
506 | Add a Helm chart for your Express.js application using the following steps:
507 |
508 | 1. Download the template Helm chart:
509 |
510 | On Linux and macOS:
511 |
512 | ```sh
513 | curl -fsSL -o main.tar.gz https://github.com/NodeShift/helm/archive/main.tar.gz
514 | ```
515 |
516 | On Windows:
517 |
518 | Download: https://github.com/NodeShift/helm/archive/main.zip
519 |
520 | 2. Untar the downloaded template chart:
521 |
522 | On Linux and macOS:
523 |
524 | ```sh
525 | tar xfz main.tar.gz
526 | ```
527 | On Windows:
528 | * Right Click on `helm-main.zip` -> Extract All... -> Select the nodeserver directory -> Extract
529 |
530 | 3. Move the chart to your projects root directory:
531 |
532 | On Linux and macOS:
533 |
534 | ```sh
535 | mv helm-main/chart chart
536 | rm -rf helm-main main.tar.gz
537 | ```
538 |
539 | On Windows **Command Prompt**:
540 |
541 | ```
542 | move helm-main\chart chart
543 | rmdir /s /q helm-main
544 | ```
545 |
546 | The provided Helm chart provides a number of configuration files, with the configurable values extracted into `chart/nodeserver/values.yaml`. In this file you provide the name of the Docker image to use, the number of replicas (instances) to deploy, etc.
547 |
548 | Go ahead and modify the `chart/nodeserver/values.yaml` file to use your image, and to deploy 3 replicas:
549 |
550 | 1. Open the `chart/nodeserver/values.yaml` file
551 | 1. Ensure that the `pullPolicy` is set to `IfNotPresent`
552 | 1. Change the `replicaCount` value to `3` (Line 3)
553 |
554 | The `repository` field gives the name of the Docker image to use. The `pullPolicy` change tells Kubernetes to use a local container image if there is one available rather than always pulling the container image from a remote repository. Finally, the `replicaCount` states how many instances to deploy.
555 |
556 | ## 6. Deploying your Application to Kubernetes
557 |
558 | Now that you have built a Helm chart for your application, the process for deploying your application has been greatly simplified.
559 |
560 | Deploy your Express.js application into Kubernetes using the following steps:
561 |
562 | 1. Create a local image registry
563 | You will need to push the image into the Kubernetes container registry so that
564 | minikube can access it.
565 |
566 | First, we enable the image registry addon for minikube:
567 |
568 | ```
569 | minikube addons enable registry
570 | ```
571 |
572 | console output:
573 | ```console
574 | $ minikube addons enable registry
575 | ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮
576 | │ │
577 | │ Registry addon with podman driver uses port 42795 please use that instead of default port 5000 │
578 | │ │
579 | ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯
580 | 📘 For more information see: https://minikube.sigs.k8s.io/docs/drivers/podman
581 | ▪ Using image registry:2.7.1
582 | ▪ Using image gcr.io/google_containers/kube-registry-proxy:0.4
583 | 🔎 Verifying registry addon...
584 | 🌟 The 'registry' addon is enabled
585 | ```
586 |
587 | _**Note**: As the message indicates, be sure you use the correct port instead of 5000_. If you
588 | don't see the warning then just use 5000 for the port in the instructions below.
589 |
590 | On Linux and macOS export a variable with the registry with:
591 |
592 | ```console
593 | export MINIKUBE_REGISTRY=$(minikube ip):
594 | ```
595 |
596 | replacing with the port listed when you ran `minikube addons enable registry`.
597 |
598 | On Windows, export a variable with the registry IP by first running `minikube ip` to get the IP of the registry and then exporting the variable:
599 |
600 | ```
601 | set MINIKUBE_REGISTRY=:
602 | ```
603 |
604 | We can now build the image directly using `minikube image build`:
605 | On Linux and macOS:
606 |
607 | ```console
608 | minikube image build -t $MINIKUBE_REGISTRY/nodeserver:1.0.0 --file Dockerfile-run .
609 | ```
610 |
611 | On Windows:
612 |
613 | ```console
614 | minikube image build -t %MINIKUBE_REGISTRY%/nodeserver:1.0.0 --file Dockerfile-run .
615 | ```
616 |
617 | And we can list the images in minikube:
618 |
619 | ```console
620 | minikube image ls
621 | ```
622 |
623 | Console output
624 |
625 | ```console
626 | 192.168.58.2:42631/nodeserver:1.0.0
627 | ```
628 |
629 | Next, we push the image into the registry using:
630 |
631 | On Linux and macOS:
632 |
633 | ```console
634 | minikube image push $MINIKUBE_REGISTRY/nodeserver
635 | ```
636 |
637 | On Windows:
638 |
639 | ```console
640 | minikube image push %MINIKUBE_REGISTRY%/nodeserver
641 | ```
642 |
643 | Finally, we can install the Helm chart using:
644 |
645 | On Linux and macOS:
646 |
647 | ```sh
648 | helm install nodeserver \
649 | --set image.repository=$MINIKUBE_REGISTRY/nodeserver chart/nodeserver
650 | ```
651 |
652 | On Windows:
653 |
654 | ```sh
655 | helm install nodeserver --set image.repository=%MINIKUBE_REGISTRY%/nodeserver chart/nodeserver
656 | ```
657 |
658 | _**Note(Mac)**: If you cannot open the Helm CLI due Apple malicious software checks, control-click the Helm application icon in the Finder and select `Open`.
659 | ([Instructions Reference](https://support.apple.com/guide/mac-help/apple-cant-check-app-for-malicious-software-mchleab3a043/mac))
660 |
661 | 2. Check that all the "pods" associated with your application are running:
662 |
663 | ```sh
664 | minikube kubectl -- get pods
665 | ```
666 |
667 | In earlier steps, we set the `replicaCount` to `3`, so you should expect to see three `nodeserver-deployment-*` pods running.
668 |
669 | Now everything is up and running in Kubernetes. It is not possible to navigate to `localhost:3000` as usual because your cluster isn't part of the localhost network, and because there are several instances to choose from.
670 |
671 | Kubernetes has a concept of a 'Service', which is an abstract way to expose an application running on a set of Pods as a network service. To access our service, we need to forward the port of the `nodeserver-service` to our local device:
672 |
673 | 3. You can forward the `nodeserver-service` to your device by:
674 |
675 | ```sh
676 | minikube kubectl -- port-forward service/nodeserver-service 3000
677 | ```
678 |
679 | You can now access the application endpoints from your browser.
680 |
681 | ## 7. Monitoring your Application with Prometheus
682 |
683 | Installing Prometheus into Kubernetes can be done using its provided Helm chart. This step needs to be done in a new Terminal window as you'll need to keep the application port-forwarded to `localhost:3000`.
684 |
685 | ```sh
686 | minikube kubectl -- create namespace prometheus
687 | minikube kubectl -- config set-context --current --namespace=prometheus
688 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
689 | helm repo update
690 | helm install prometheus prometheus-community/prometheus --namespace=prometheus
691 | ```
692 |
693 | You can then run the following two commands to be able to connect to Prometheus from your browser:
694 |
695 | On Linux and macOS:
696 | ```sh
697 | export POD_NAME=$(minikube kubectl -- get pods --namespace prometheus -l "app=prometheus,component=server" -o jsonpath="{.items[0].metadata.name}")
698 | minikube kubectl -- --namespace prometheus port-forward $POD_NAME 9090
699 | ```
700 |
701 | On Windows **Command Prompt**:
702 | ```
703 | for /f "tokens=*" %i in ('"minikube kubectl -- get pods --namespace prometheus -l app=prometheus,component=server -o jsonpath={.items[0].metadata.name}"') do set POD_NAME=%i
704 | minikube kubectl -- --namespace prometheus port-forward %POD_NAME% 9090
705 | ```
706 |
707 | This may fail with a warning about the status being "Pending" until Prometheus has started, retry once the status is "Running" for all pods:
708 | ```sh
709 | minikube kubectl -- -n prometheus get pods --watch
710 | ```
711 |
712 | You can now connect to Prometheus at [http://localhost:9090](http://localhost:9090).
713 |
714 | This should show the following screen:
715 |
716 | 
717 |
718 | Prometheus will be automatically collecting data from your Express.js application, allowing you to create graphs of your data.
719 |
720 | To build your first graph, type `nodejs_heap_size_used_bytes` into the **Expression** box and click on the **Graph** tab.
721 |
722 | This will show a graph mapping the `nodejs_heap_size_used_bytes` across each of the three pods. You'll probably want to reduce the graph interval to `1m` (1 minute) so that you can start seeing the changes in the graph. You can also try sending some requests to your server (http://localhost:3000) in another browser window to add load to the server, which you should then see reflected in the graph.
723 |
724 | 
725 |
726 |
727 | Whilst Prometheus provides the ability to build simple graphs and alerts, Grafana is commonly used to build more sophisticated dashboards.
728 |
729 | ### Installing Grafana into Kubernetes
730 |
731 | Installing Grafana into Kubernetes can be done using its provided Helm chart.
732 |
733 | In a third Terminal window:
734 |
735 | ```sh
736 | minikube kubectl -- create namespace grafana
737 | minikube kubectl -- config set-context --current --namespace=grafana
738 | helm repo add grafana https://grafana.github.io/helm-charts
739 | helm install grafana grafana/grafana --set adminPassword=PASSWORD --namespace=grafana
740 | ```
741 |
742 | You can then run the following two commands to be able to connect to Grafana from your browser:
743 |
744 | On Linux and macOS:
745 | ```sh
746 | export POD_NAME=$(minikube kubectl -- get pods --namespace grafana -o jsonpath="{.items[0].metadata.name}")
747 | minikube kubectl -- --namespace grafana port-forward $POD_NAME 3001:3000
748 | ```
749 |
750 | On Windows **Command Prompt**:
751 | ```
752 | for /f "tokens=*" %i in ('"minikube kubectl -- get pods --namespace grafana -o jsonpath={.items[0].metadata.name}"') do set POD_NAME=%i
753 | minikube kubectl -- --namespace grafana port-forward %POD_NAME% 3001:3000
754 | ```
755 |
756 | You can now connect to Grafana at the following address, using `admin` and `PASSWORD` to login:
757 |
758 | * [http://localhost:3001](http://localhost:3001)
759 |
760 | This should show the following screen:
761 |
762 | 
763 |
764 | To connect Grafana to the Prometheus service, go to http://localhost:3001/datasources and click `Add Data Source`. Select `Prometheus`.
765 |
766 | This opens a panel that should be filled out with the following entries:
767 |
768 | * Name: `Prometheus`
769 | * URL: `http://prometheus-server.prometheus.svc.cluster.local`
770 |
771 | 
772 |
773 | Now click on `Save & Test` to check the connection and save the Data Source configuration.
774 |
775 | Grafana now has access to the data from Prometheus.
776 |
777 | ### Installing a Kubernetes Dashboard into Grafana
778 |
779 | The Grafana community provides a large number of pre-created dashboards which are available for download, including some which are designed to display Kubernetes data.
780 |
781 | To install one of those dashboards, expand the **Dashboards** menu on the left sidebar and click on the `+ Import`.
782 |
783 | In the provided panel, enter `1621` into the `Import via Grafana.com` field to import dashboard number 1621, press `Load` and the `Import`.
784 |
785 | **Note**: If `1621` is not recognized, it may be necessary to download the JSON for [1621](https://grafana.com/grafana/dashboards/1621) (select `Download JSON`), and use `Upload JSON` in the Grafana UI.
786 |
787 | This then loads the information on dashboard `1621` from Grafana.com.
788 |
789 | 
790 |
791 | Change the `Prometheus` field so that it is set to `Prometheus` and click `Import`.
792 |
793 | This will then open the dashboard, which will automatically start populating with data about your Kubernetes cluster.
794 |
795 | 
796 |
797 | ### Adding Custom Graphs
798 |
799 | To extend the dashboard with your own graphs, click the `Add panel` icon on the top toolbar
800 |
801 | 
802 |
803 | and select `Add new Panel`
804 |
805 | _**Note:**_ On some Grafana versions, after you click `Add panel` in the toolbar, it is necessary to select `Choose Visualization` before selecting `Graph`.
806 |
807 | This opens an editor panel where you can select data that you'd like to graph.
808 |
809 | In the bottom half of the page, click on the Query tab, and validate that Data Source is set to Prometheus.
810 |
811 | Type `nodejs_heap_size_used_bytes` into the `Metrics` box, click on the Run Queries button and a graph of your application's process heap size used from Node.js will be shown. You may need to click the `Query` icon on the left to access the `Metrics` box.
812 | 
813 |
814 | You now have integrated monitoring for both your Kubernetes cluster and your deployed Express.js application.
815 |
816 | ### Congratulations! 🎉
817 |
818 | You now have an Express.js application deployed with scaling using Docker and Kubernetes, with automatic restart, and full metrics-based monitoring enabled!
819 |
820 | Once you are finished, you should exit all the running terminal processes with `CTRL + C`, and then use the following commands to delete the helm releases and remove your Kubernetes pods:
821 |
822 | ```sh
823 | helm delete nodeserver -n default
824 | helm delete prometheus -n prometheus
825 | helm delete grafana -n grafana
826 | ```
827 |
828 | To change your Kubernetes context back to default use:
829 |
830 | ```sh
831 | minikube kubectl -- config set-context --current --namespace=default
832 | ```
833 |
--------------------------------------------------------------------------------
/cloud-native/images/add_panel_graphana_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/add_panel_graphana_icon.png
--------------------------------------------------------------------------------
/cloud-native/images/build_image_nodeserver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/build_image_nodeserver.png
--------------------------------------------------------------------------------
/cloud-native/images/grafana_datasource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/grafana_datasource.png
--------------------------------------------------------------------------------
/cloud-native/images/grafana_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/grafana_home.png
--------------------------------------------------------------------------------
/cloud-native/images/grafana_import.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/grafana_import.png
--------------------------------------------------------------------------------
/cloud-native/images/grafana_metric.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/grafana_metric.png
--------------------------------------------------------------------------------
/cloud-native/images/grafana_monitor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/grafana_monitor.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-codewind-launch-nodejs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-codewind-launch-nodejs.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-codewind-new-nodejs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-codewind-new-nodejs.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-codewind-nodejs-running.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-codewind-nodejs-running.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-codewind-performance-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-codewind-performance-2.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-codewind-performance-test-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-codewind-performance-test-2.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-codewind-performance-test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-codewind-performance-test.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-codewind-performance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-codewind-performance.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-codewind-shell-commands.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-codewind-shell-commands.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-codewind-shell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-codewind-shell.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-codewind-template-sources.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-codewind-template-sources.png
--------------------------------------------------------------------------------
/cloud-native/images/kab-workshop-new-project.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/kab-workshop-new-project.png
--------------------------------------------------------------------------------
/cloud-native/images/nodeserver_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/nodeserver_image.png
--------------------------------------------------------------------------------
/cloud-native/images/podman_build_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/podman_build_image.png
--------------------------------------------------------------------------------
/cloud-native/images/podman_desktop_images.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/podman_desktop_images.png
--------------------------------------------------------------------------------
/cloud-native/images/prom_graph2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/prom_graph2.png
--------------------------------------------------------------------------------
/cloud-native/images/prometheus_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/prometheus_graph.png
--------------------------------------------------------------------------------
/cloud-native/images/run_nodeserver_container.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/cloud-native/images/run_nodeserver_container.png
--------------------------------------------------------------------------------
/conferences/nodeconf.eu-2021.md:
--------------------------------------------------------------------------------
1 | # Nodeconf Remote 2021
2 |
3 | ## Welcome from Red Hat!
4 |
5 | Red Hat is excited to be back at [NodeConf Remote](https://www.nodeconfremote.com/)! We'll be demonstrating a few of our favorite production-quality tools and solutions, all designed to help teams maintain productivity while successfully navigating the vast and rapidly changing cloud-native landscape.
6 |
7 | ## Developing Cloud Native Node.js Applications
8 |
9 | This workshop provides an introduction to cloud native development with Node.js. The workshop will walk you through building cloud native Node.js applications, including demonstrating typical components that form part of the Red Hat and IBM Node.js Reference Architecture. Next, we’ll take you through a full stack deployment on Kubernetes. The workshop will cover key cloud native concepts and technologies, including health checks, metrics, Docker, Kubernetes, Prometheus, Grafana.
10 |
11 | ## Agenda
12 |
13 | * Cloud Native concepts Introduction
14 | * Link to Slides - [Slides](../conferences/slides/Node_js_in_the_cloud.pdf)
15 |
16 | * Cloud Native concepts Tutorial
17 | * Self-paced workshop - [instructions](../cloud-native/README.md)
18 |
19 | * Building your first REST API
20 | * Self-paced workshop - [instructions](../api/README.md)
21 |
22 | * Deployinhg Full stack JavaScript to Red Hat OpenShift
23 | * [Interactive Tutorial](https://developers.redhat.com/developer-sandbox/activities/deploying-full-stack-javascript-applications-to-the-sandbox/part1)
24 |
25 | ## Resources
26 | * [The Red Hat and IBM Node.js Reference architecture.](https://nodeshift.dev/nodejs-reference-architecture/)
27 | * The teams opinion on what components our customers and internal teams
28 | should use when building Node.js applications and guidance for how to be successful in production with those components.
29 |
30 | * [Red Hat Node.js Topic Page](https://developers.redhat.com/topics/nodejs)
31 |
32 | ## Contacts
33 | * Bethany Griggs - [Twitter](https://twitter.com/BethGriggs_)
34 | * Lucas Holmquist - [Twitter](https://twitter.com/sienaluke)
35 | * Michael Dawson - [Twitter](https://twitter.com/mhdawson1)
36 | * Joe Sepi - [Twitter](https://twitter.com/joe_sepi)
37 | * Ryan Jarvinen - [Twitter](https://twitter.com/ryanj)
38 |
39 |
--------------------------------------------------------------------------------
/conferences/nodeconf.eu-2022.md:
--------------------------------------------------------------------------------
1 | # Nodeconf Remote 2022
2 |
3 | ## Welcome from Red Hat!
4 |
5 | Red Hat is excited to be back at [NodeConf.eu](https://www.nodeconf.eu/)! We'll be demonstrating a few of our favorite production-quality tools and solutions, all designed to help teams maintain productivity while successfully navigating the vast and rapidly changing cloud-native landscape.
6 |
7 | ## Developing Cloud Native Node.js Applications
8 |
9 | This workshop provides an introduction to cloud native development with Node.js. The workshop will walk you through building cloud native Node.js applications, including demonstrating typical components that form part of the Red Hat and IBM Node.js Reference Architecture. Next, we’ll take you through a full stack deployment on Kubernetes. The workshop will cover key cloud native concepts and technologies, including health checks, metrics, Docker, Kubernetes, Prometheus, Grafana.
10 |
11 | ## Agenda
12 |
13 | * Prerequisites
14 | * If possible please install the
15 | [prerequisites](https://github.com/nodeshift/tutorial/tree/main/cloud-native#prerequisites)
16 | in advance and complete associated [setup](https://github.com/nodeshift/tutorial/tree/main/cloud-native#setting-up).
17 | This will reduce the load on the wifi and maximize the use of your time during the workshop itself.
18 |
19 | **Note:** admin access is required to install a number of the prerequisites.
20 |
21 | * Tutorial
22 | * Self-paced workshop - [instructions](../cloud-native/README.md)
23 | * Introduction to Kube Service Bindings(Optional) - [instructions](https://github.com/nodeshift-blog-examples/kube-service-bindings-examples/tree/main/src/mongodb)
24 |
25 | ## Resources
26 | * [The Red Hat and IBM Node.js Reference architecture.](https://nodeshift.dev/nodejs-reference-architecture/)
27 | * The teams opinion on what components our customers and internal teams should use when building Node.js applications
28 | and guidance for how to be successful in production with those components.
29 |
30 | * [Red Hat Node.js Topic Page](https://developers.redhat.com/topics/nodejs)
31 |
32 | ## Contacts
33 | * Bethany Griggs
34 | * Lucas Holmquist
35 | * Michael Dawson
36 |
37 |
--------------------------------------------------------------------------------
/conferences/slides/Node_js_in_the_cloud.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/conferences/slides/Node_js_in_the_cloud.pdf
--------------------------------------------------------------------------------
/helm/README.md:
--------------------------------------------------------------------------------
1 | # Deploying your Node.js App via Helm
2 |
3 | ## Packaging up your Node.js application into a Helm Chart
4 |
5 | This will show you how to create your own Helm Chart for your Node.js Application and how to tweak it to best fit your applications needs.
6 |
7 | In this self-paced tutorial you will:
8 |
9 | - Create a Helm Chart
10 | - Add a dependency chart
11 | - Create liveness probes
12 |
13 | The application you will use is the one created from - https://github.com/nodeshift/mern-workshop
14 |
15 | ## Prerequisites
16 |
17 | Before getting started, make sure you have the following prerequisites installed on your system.
18 |
19 | 1. Install [Node.js 16](https://nodejs.org/en/download/) (or use [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) for linux, mac or [nvm-windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) for windows)
20 | 1. Podman v4 (and above)
21 | - **On Mac**: [Podman](https://podman.io/getting-started/installation#macos)
22 | - **On Windows**: Skip this step, as for installing Podman you will get prompt during Podman Desktop installation.
23 | - **On Linux**: [Podman](https://podman.io/getting-started/installation#installing-on-linux)
24 | 1. Podman Desktop
25 | - **On Mac**: [Podman Desktop](https://podman-desktop.io/downloads/macOS)
26 | - **On Windows**: [Podman Desktop](https://podman-desktop.io/downloads/windows)
27 | - **On Linux**: [Podman Desktop](https://podman-desktop.io/downloads/linux)
28 | 1. Kubernetes
29 | - **On Mac**: [minikube](https://minikube.sigs.k8s.io/docs/start/)
30 | - **On Windows**: [minikube](https://minikube.sigs.k8s.io/docs/start/)
31 | - **On Linux**: [minikube](https://minikube.sigs.k8s.io/docs/start/)
32 | 1. Helm v3 - [Installation](./README.md#installing-helm-v37)
33 | - **Note**: This workshop tested with Helm v3.7
34 |
35 | ## Setting up
36 |
37 | ### Starting Podman Machine
38 |
39 | #### Linux
40 |
41 | Nothing to do, no Podman machine is required on Linux
42 |
43 | #### On Mac
44 |
45 | After installing podman, open a terminal and run the below commands to initialize and run the podman machine:
46 |
47 | _**NOTE:** \*On Apple M1 Pro chip, the system version has to be 12.4 and above._
48 |
49 | ```
50 | podman machine init --cpus 2 --memory 8096 --disk-size 20
51 | podman machine start
52 | podman system connection default podman-machine-default-root
53 | ```
54 |
55 | #### On Windows
56 |
57 | 1. Launch Podman Desktop and on the home tab click on **install podman**. In case of any missing parts for podman installation (e.x. wsl, hyper-v, etc.) follow the instructions indicated by Podman Desktop on the home page. In that case you might need to reboot your system several times.
58 |
59 | 1. After installing podman, set WSL2 as your default WSL by entering below command in PowerShell (with administration priviledges).
60 |
61 | ```
62 | wsl --set-default-version 2
63 | ```
64 |
65 | 1. On Podman Desktop Home tab -> click on **initialize Podman** -> wait till the initialization is finished
66 | 1. On Podman Desktop Home tab -> click on **Run Podman** to run podman.
67 |
68 | #### On Windows **Home**
69 |
70 | 1. Downlodad podman from https://github.com/containers/podman/releases the Windows installer file is named podman-v.#.#.#.msi
71 | 1. Run the MSI file
72 | 1. Launch as Administrator a new Command Prompt
73 | 1. On the Command Prompt run:
74 | ```
75 | podman machine init
76 | podman machine set --rootful
77 | podman machine start
78 | ```
79 | 1. Launch Podman Desktop to see and manage your containers, images, etc.
80 |
81 | ### Starting Kubernetes
82 |
83 | #### On Mac:
84 |
85 | 1. Install Minikube
86 |
87 |
88 | Download binary file (click to expand)
89 |
90 | **x86-64**
91 |
92 | ```
93 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64
94 | ```
95 |
96 | **ARM64**
97 |
98 | ```
99 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-arm64
100 | ```
101 |
102 |
103 |
104 | Add minikube binary file to your `PATH system variable`
105 |
106 | ```
107 | chmod +x minikube-darwin-*
108 | mv minkube-linux-* /usr/local/bin/minikube
109 | ```
110 |
111 | 1. start minikube
112 | ```
113 | minikube start --driver=podman --container-runtime=cri-o
114 | ```
115 |
116 | #### On Windows:
117 |
118 | 1. Download minikube
119 | ```
120 | https://github.com/kubernetes/minikube/releases/latest/download/minikube-windows-amd64.exe
121 | ```
122 | 1. Rename `minikube-windows-amd64.exe` to `minikube.exe`
123 | 1. Move minikube under `C:\Program Files\minikube` directory
124 |
125 | 1. Add `minikube.exe` binary to your `PATH system variable`
126 |
127 | 1. Right-click on the **Start Button** -> Select **System** from the context menu -> click on **Advanced system settings**
128 | 1. Go to the **Advanced** tab -> click on **Environment Variables** -> click the variable called **Path** -> **Edit**
129 | 1. Click **New** -> Enter the path to the folder containing the binary e.x. `C:\Program Files\minikube` -> click **OK** to save the changes to your variables
130 | 1. Start Podman Desktop and click on run podman
131 |
132 | 1. Start minikube:
133 |
134 | - For windows Start minikube by opening Powershell or Command Prompt **as administrator** and enter below command.
135 |
136 | ```
137 | minikube start
138 | ```
139 |
140 | - For windows **Home** Start minikube by opening Powershell or Command Prompt **as administrator** and enter below command.
141 |
142 | ```
143 | minikube start --driver=podman --container-runtime=containerd
144 | ```
145 |
146 | #### On Linux
147 |
148 | 1. Install Minikube
149 |
150 |
151 | Download binary file (click to expand)
152 |
153 | **x86-64**
154 |
155 | ```
156 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
157 | ```
158 |
159 | **ARM64**
160 |
161 | ```
162 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm64
163 | ```
164 |
165 | **ARMv7**
166 |
167 | ```
168 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm
169 | ```
170 |
171 | **ppc64**
172 |
173 | ```
174 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-ppc64le
175 | ```
176 |
177 | **S390x**
178 |
179 | ```
180 | curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-s390x
181 | ```
182 |
183 |
184 |
185 | Add minikube binary file to your `PATH system variable`
186 |
187 | ```
188 | chmod +x minikube-linux-*
189 | mv minkube-linux-* /usr/local/bin/minikube
190 | ```
191 |
192 | 1. Change minikube for starting podman rootless
193 | https://minikube.sigs.k8s.io/docs/drivers/podman/#rootless-podman
194 |
195 | ```
196 | minikube config set rootless true
197 | ```
198 |
199 | 1. start minikube
200 |
201 | ```
202 | minikube start --driver=podman --container-runtime=containerd
203 | ```
204 |
205 | 1. Possible additional steps needed
206 | - delegation also needed on Unbuntu 2022 - https://rootlesscontaine.rs/getting-started/common/cgroup2/
207 |
208 | ### Installing Helm v3.7
209 |
210 | Helm is a package manager for Kubernetes. By installing a Helm "chart" into your Kubernetes cluster you can quickly run all kinds of different applications. You can install Helm by downloading the binary file and adding it to your PATH:
211 |
212 | 1. Download the binary file from the section **Installation and Upgrading** for Operating system accordingly.
213 |
214 | - https://github.com/helm/helm/releases/tag/v3.7.2
215 |
216 | 1. Extract it:
217 |
218 | - On Linux:
219 | ```
220 | tar -zxvf helm-v3.7.2-*
221 | ```
222 | - On Windows: **Right Click** on `helm-v3.7.2-windows-amd64` zipped file -> **Extract All** -> **Extract**
223 | - On Mac:
224 | ```
225 | tar -zxvf helm-v3.7.2-*
226 | ```
227 |
228 | 1. Add helm binary file to your `PATH system variable`
229 |
230 | On Linux and Mac (sudo required for cp step on linux):
231 |
232 | ```
233 | cp `.//helm` /usr/local/bin/helm
234 | rm -rf ./
235 | ```
236 |
237 | If running on Mac results in a pop up indicating that the app could not be verified,
238 | you will need to go to Apple menu > System Preferences, click Security & Privacy and
239 | allow helm to run.
240 |
241 | On Windows:
242 |
243 | 1. Move helm binary file to `C:\Program Files\helm`
244 | 1. Right-click on the **Start Button** -> Select **System** from the context menu -> click on **Advanced system settings**
245 | 1. Go to the **Advanced** tab -> click on **Environment Variables** -> click the variable called **Path** -> **Edit**
246 | 1. Click **New** -> Enter the path to the folder containing the binary e.x. `C:\Program Files\helm` -> click **OK** to save the changes to your variables
247 |
248 | ## Downloading the application
249 |
250 | Make sure you clone down the application repo and you are in the correct directory.
251 |
252 | ```sh
253 | $ git clone https://github.com/nodeshift/mern-workshop.git
254 | $ cd mern-workshop
255 | ```
256 |
257 | ### 1. Create your Helm Chart files
258 |
259 | In your terminal inside the folder holding your application run:
260 |
261 | ```sh
262 | $ mkdir chart
263 | $ cd chart
264 | $ helm create myapp
265 | ```
266 |
267 | This will create the following file structure:
268 |
269 | ```
270 | myapp/
271 | ├── .helmignore # Contains patterns of which files to ignore when packaging your Helm Chart.
272 | ├── Chart.yaml # Information about your chart
273 | ├── values.yaml # The default values for your templates
274 | ├── charts/ # Charts that this chart depends on
275 | └── templates/ # The template files
276 | └── tests/ # The test files
277 | ```
278 |
279 | These files will form the basis of your Helm Chart, lets explore the pre-created files and make some necessary changes.
280 |
281 | ### 2. Editing the .helmignore file
282 |
283 | This file works the same as any .ignore file, you just fill in the patterns you don't want to be packaged up into the helm chart. For example, if you have some secrets saved as a JSON file that you do not want to be inside the helm chart. For our application we do not have any files we need to protect so let's move on to the next step.
284 |
285 | ### 3. The Chart.yaml file
286 |
287 | This file contains the base information about your Helm Chart, your premade one should look similar to this (with extra comments):
288 |
289 | ```yaml
290 | apiVersion: v2
291 | name: myapp
292 | description: A Helm chart for Kubernetes
293 | type: application
294 | version: 0.1.0
295 | appVersion: 1.0.0
296 | ```
297 |
298 | `apiVersion: v2` signals that this chart is designed for Helm3 support _only_.
299 |
300 | `version` is the charts version, increment this under semver every time you update the chart
301 |
302 | `appVersion` is the version of the app you are deploying, this is to be increased every time you increase the version of your app but does not impact the charts version
303 |
304 | The rest of the fields are self explanatory but lets add some more information to describe our chart. We are going to set a Kubernetes minimum version, add some descriptive keywords and add our name as Maintainers. So go ahead and add the following to your `Chart.yaml` whilst subsituting your name and email in:
305 |
306 | ```yaml
307 | kubeVersion: ">= 1.21.0-0"
308 | keywords:
309 | - nodejs
310 | - express
311 | - mern
312 | maintainers:
313 | - name: Firstname Lastname
314 | email: FirstnameLastname@company.com
315 | ```
316 |
317 | The final key thing we are going to add is a dependency, our application needs mongoDB to run so we are going to call an existing mongo chart to install mongo as we install our chart. Firstly we need to add to our `Chart.yaml`:
318 |
319 | ```yaml
320 | dependencies:
321 | - name: mongodb
322 | version: ">= 10.26.3"
323 | repository: https://charts.bitnami.com/bitnami
324 | ```
325 |
326 | Then run the following command in the terminal to download the chart:
327 |
328 | ```sh
329 | cd myapp
330 | helm dependency update
331 | cd ..
332 | ```
333 |
334 | ### 4. Template files
335 |
336 | Inside the templates folder you will find that Helm has created some files for us already:
337 |
338 | ```sh
339 | $ ls myapp/templates
340 |
341 | NOTES.txt
342 | _helpers.tpl
343 | deployment.yaml
344 | hpa.yaml
345 | ingress.yaml
346 | service.yaml
347 | serviceaccount.yaml
348 | tests
349 | ```
350 |
351 | These files represent the kubernetes objects that our Helm Chart will deploy - you can set up the deployment and service, and you can also create a serviceaccount for your app to use. The `NOTES.txt` file is just the notes that will be displayed to the user when they deploy your Helm Chart, you can use this to provide further instruction to them to get your application working. `_helpers.tpl` is where template helpers that you can re-use throughout the chart go. However for this tutorial we are going to use our own files so you can go ahead and remove the generated files:
352 |
353 | ```sh
354 | $ rm -rf chart/myapp/templates/*
355 | ```
356 |
357 | Now let's move on to making our own template files!
358 |
359 | #### 4.1 Frontend Service
360 |
361 | The first template we will create will deploy the service for our frontend. Let's create a file called `chart/myapp/templates/frontend-service.yaml` and paste in the following:
362 |
363 | ```yaml
364 | apiVersion: v1
365 | kind: Service
366 | metadata:
367 | annotations:
368 | prometheus.io/scrape: 'true'
369 | name: "frontend-service"
370 | spec:
371 | ports:
372 | - name: http
373 | port: {{ .Values.frontend.service.servicePort }}
374 | nodePort: 30444
375 | type: {{ .Values.frontend.service.type }}
376 | selector:
377 | app: "frontend-selector"
378 | ```
379 |
380 | This will create the service that serves our frontend and allows for it to be connected to the outside world. `nodePort` is the port we want our service to be accessible through so `localhost:30444` will take us to our frontend once deployed
381 |
382 | #### 4.2 Frontend Deployment
383 |
384 | The next template we create will be the deployment for the frontend. Create a file called `chart/myapp/templates/frontend-deployment.yaml` and paste in the following:
385 |
386 | ```yaml
387 | apiVersion: apps/v1
388 | kind: Deployment
389 | metadata:
390 | name: frontend-deployment
391 | labels:
392 | chart: frontend
393 | spec:
394 | selector:
395 | matchLabels:
396 | app: "frontend-selector"
397 | template:
398 | metadata:
399 | labels:
400 | app: "frontend-selector"
401 | spec:
402 | containers:
403 | - name: frontend
404 | image: "{{ .Values.frontend.image.repository }}/{{ .Values.frontend.image.tag }}"
405 | imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
406 | livenessProbe:
407 | httpGet:
408 | path: /
409 | port: {{ .Values.frontend.service.servicePort }}
410 | initialDelaySeconds: {{ .Values.frontend.livenessProbe.initialDelaySeconds}}
411 | periodSeconds: {{ .Values.frontend.livenessProbe.periodSeconds}}
412 | ports:
413 | - containerPort: {{ .Values.frontend.service.servicePort}}
414 | ```
415 |
416 | What this creates is our frontend deployment, inside that is the pod that is running our frontend image plus its replicaset.
417 |
418 | `image` is the image we want the container to deploy using variables pulled from our `values.yaml` file.
419 |
420 | We also add the `frontend-selector` label to our pod which allows for our frontend service to connect with it.
421 |
422 | #### 4.3 Backend Service
423 |
424 | Our third file will spawn the service for the backend part of our application. Create a file called `chart/myapp/templates/backend-service.yaml` and paste in the following:
425 |
426 | ```yaml
427 | apiVersion: v1
428 | kind: Service
429 | metadata:
430 | annotations:
431 | prometheus.io/scrape: 'true'
432 | name: "backend-service"
433 | spec:
434 | ports:
435 | - name: http
436 | port: {{ .Values.backend.service.servicePort }}
437 | nodePort: 30555
438 | type: NodePort
439 | selector:
440 | app: "backend-selector"
441 | ```
442 |
443 | This file does the same thing as `frontend-service.yaml` but for the backend and under a different port.
444 |
445 | #### 4.4 Backend Deployment
446 |
447 | Our final template is the deployment for the backend. Create a file called `chart/myapp/templates/backend-deployment.yaml` and paste in the following:
448 |
449 | ```yaml
450 | apiVersion: apps/v1
451 | kind: Deployment
452 | metadata:
453 | name: backend-deployment
454 | labels:
455 | chart: backend
456 | spec:
457 | selector:
458 | matchLabels:
459 | app: "backend-selector"
460 | template:
461 | metadata:
462 | labels:
463 | app: "backend-selector"
464 | spec:
465 | containers:
466 | - name: backend
467 | image: "{{ .Values.backend.image.repository }}/{{ .Values.backend.image.tag }}"
468 | imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
469 | livenessProbe:
470 | httpGet:
471 | path: /health
472 | port: {{ .Values.backend.service.servicePort }}
473 | initialDelaySeconds: {{ .Values.backend.livenessProbe.initialDelaySeconds}}
474 | periodSeconds: {{ .Values.backend.livenessProbe.periodSeconds}}
475 | ports:
476 | - containerPort: {{ .Values.backend.service.servicePort}}
477 | env:
478 | - name: PORT
479 | value : "{{ .Values.backend.service.servicePort }}"
480 | - name: APPLICATION_NAME
481 | value: "{{ .Release.Name }}"
482 | - name: MONGO_URL
483 | value: {{ .Values.backend.services.mongo.url }}
484 | - name: MONGO_DB_NAME
485 | value: {{ .Values.backend.services.mongo.name }}
486 | ```
487 |
488 | Similar to our frontend deployment file, this file creates our backend deployment, with a key difference being the mongoDB information that is passed through to allow for communication with the mongo instance.
489 |
490 | ### 5. Values file
491 |
492 | For the `chart/myapp/values.yaml` file we are going to split it into 3 sections, have a read of each section and then add them all to your `values.yaml` file
493 |
494 | ```yaml
495 | # Frontend
496 | frontend:
497 | replicaCount: 1
498 | revisionHistoryLimit: 1
499 | image:
500 | repository: frontend
501 | tag: frontend:v1.0.0
502 | pullPolicy: IfNotPresent
503 | resources:
504 | requests:
505 | cpu: 200m
506 | memory: 300Mi
507 | livenessProbe:
508 | initialDelaySeconds: 30
509 | periodSeconds: 10
510 | service:
511 | name: frontend
512 | type: NodePort
513 | servicePort: 80 # the port where nginx serves its traffic
514 | ```
515 |
516 | These values are for the frontend section. Here we pass through the image name, tag and the values for the frontend service.
517 |
518 | ```yaml
519 | # backend
520 | backend:
521 | replicaCount: 1
522 | revisionHistoryLimit: 1
523 | image:
524 | repository: backend
525 | tag: backend:v1.0.0
526 | pullPolicy: IfNotPresent
527 | resources:
528 | requests:
529 | cpu: 200m
530 | memory: 300Mi
531 | livenessProbe:
532 | initialDelaySeconds: 30
533 | periodSeconds: 10
534 | service:
535 | name: backend
536 | type: NodePort
537 | servicePort: 30555
538 | services:
539 | mongo:
540 | url: myapp-mongodb
541 | name: todos
542 | env: production
543 | ```
544 |
545 | These values are for the backend section. Here we pass through the image, tag, service information, and some mongoDB information to locate the instance.
546 |
547 | ```yaml
548 | # mongo
549 | mongodb:
550 | auth:
551 | enabled: false
552 | replicaSet:
553 | enabled: true
554 | replicas:
555 | secondary: 3
556 | service:
557 | type: LoadBalancer
558 | ```
559 |
560 | Finally these values are passed through to the mongoDB chart we downloaded earlier.
561 |
562 | ## 6. Deploying your Application to Kubernetes
563 |
564 | Now that you have built a Helm chart for your application, the process for deploying your application has been greatly simplified.
565 |
566 | Deploy your Express.js application into Kubernetes using the following steps:
567 |
568 | 1. Create a local image registry
569 | You will need to push the image into the kubernetes container registry so that
570 | minikube can access it.
571 |
572 | First we enable the image registry addon for minikube:
573 |
574 | ```
575 | minikube addons enable registry
576 | ```
577 |
578 | console output:
579 |
580 | ```console
581 | $ minikube addons enable registry
582 | ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮
583 | │ │
584 | │ Registry addon with podman driver uses port 42795 please use that instead of default port 5000 │
585 | │ │
586 | ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯
587 | 📘 For more information see: https://minikube.sigs.k8s.io/docs/drivers/podman
588 | ▪ Using image registry:2.7.1
589 | ▪ Using image gcr.io/google_containers/kube-registry-proxy:0.4
590 | 🔎 Verifying registry addon...
591 | 🌟 The 'registry' addon is enabled
592 | ```
593 |
594 | _**Note**: As the message indicates, be sure you use the correct port instead of 5000_. If you
595 | don't see the warning then just use 5000 for the port in the instructions below.
596 |
597 | On Linux and macOS export a variable with the registry with:
598 |
599 | ```console
600 | export MINIKUBE_REGISTRY=$(minikube ip):
601 | ```
602 |
603 | replacing with the port listed when you ran `minikube addons enable registry`.
604 |
605 | On Windows export a variable with the registry with by first running
606 |
607 | ```
608 | minikube ip
609 | ```
610 |
611 | to get the ip of the registry and then exporting
612 |
613 | ```
614 | set MINIKUBE_REGISTRY=:
615 | ```
616 |
617 | We can now build the image directly using `minikube image build`:
618 | On Linux and macOS:
619 |
620 | ```console
621 | cd backend
622 | minikube image build -t $MINIKUBE_REGISTRY/backend:v1.0.0 --file Dockerfile .
623 | cd ../frontend
624 | minikube image build -t $MINIKUBE_REGISTRY/frontend:v1.0.0 --file Dockerfile .
625 | ```
626 |
627 | On Windows:
628 |
629 | ```console
630 | cd ../backend
631 | minikube image build -t %MINIKUBE_REGISTRY%/backend:v1.0.0 --file Dockerfile .
632 | cd ../frontend
633 | minikube image build -t %MINIKUBE_REGISTRY%/frontend:v1.0.0 --file Dockerfile .
634 | ```
635 |
636 | And we can list the images in minikube:
637 |
638 | ```console
639 | minikube image ls
640 | ```
641 |
642 | Console output
643 |
644 | ```console
645 | :/frontend:v1.0.0
646 | :/backend:v1.0.0
647 | ```
648 |
649 | Next, we push the image into the registry using:
650 |
651 | On Linux and macOS:
652 |
653 | ```console
654 | minikube image push $MINIKUBE_REGISTRY/backend
655 | minikube image push $MINIKUBE_REGISTRY/frontend
656 | ```
657 |
658 | On Windows:
659 |
660 | ```console
661 | minikube image push %MINIKUBE_REGISTRY%/backend
662 | minikube image push %MINIKUBE_REGISTRY%/frontend
663 | ```
664 |
665 | ### 6. Deploy your Helm Chart
666 |
667 | Once the images are built you can now deploy your helm chart
668 |
669 | *Validate you are in the mern-workshop directory*
670 |
671 | On Linux and macOS:
672 |
673 | ```sh
674 | $ helm install myapp --set backend.image.repository=$MINIKUBE_REGISTRY --set frontend.image.repository=$MINIKUBE_REGISTRY chart/myapp
675 | ```
676 |
677 | On Windows:
678 |
679 | ```sh
680 | $ helm install myapp --set backend.image.repository=%MINIKUBE_REGISTRY% --set frontend.image.repository=%MINIKUBE_REGISTRY% chart/myapp
681 | ```
682 |
683 | Check your pods are running by running:
684 |
685 | ```sh
686 | $ kubectl get pods
687 | ```
688 |
689 | You should get a similar output to:
690 |
691 | ```sh
692 | NAME READY STATUS RESTARTS AGE
693 | backend-deployment-5d6bb8c5f8-xm4bv 1/1 Running 0 67s
694 | frontend-deployment-6c7779ff9d-xjdrv 1/1 Running 0 67s
695 | myapp-mongodb-64df664c5b-st8fb 1/1 Running 0 66s
696 | ```
697 |
698 | Wait for some time until mongo status is running
699 |
700 | Also, by viewing the logs of the backend service with below command
701 |
702 | ```sh
703 | kubectl logs backend-deployment-xxxxxxxx-xxxxx -f
704 | ```
705 | you should get below message
706 |
707 | `Connection is established with mongodb, details: mongodb://myapp-mongodb:27017`
708 |
709 | You can then run the following two commands in order to forward 30555 port to your backend pod:
710 |
711 | On Linux and macOS:
712 | ```sh
713 | export BACKEND_POD_NAME=$(minikube kubectl -- get pods --namespace default -o jsonpath="{.items[0].metadata.name}")
714 | minikube kubectl -- --namespace default port-forward $BACKEND_POD_NAME 30555:30555
715 | ```
716 |
717 | On Windows **Command Prompt**:
718 | ```
719 | for /f "tokens=*" %i in ('"minikube kubectl -- get pods --namespace default -o jsonpath={.items[0].metadata.name}"') do set BACKEND_POD_NAME=%i
720 | minikube kubectl -- --namespace default port-forward %BACKEND_POD_NAME% 30555:30555
721 | ```
722 |
723 | And by running the following two commands you are able to port forward your app on port 30444 on localhost:
724 |
725 | On Linux and macOS:
726 | ```sh
727 | export FRONTEND_POD_NAME=$(minikube kubectl -- get pods --namespace default -o jsonpath="{.items[1].metadata.name}")
728 | minikube kubectl -- --namespace default port-forward $FRONTEND_POD_NAME 30444:80
729 | ```
730 |
731 | On Windows **Command Prompt**:
732 | ```
733 | for /f "tokens=*" %i in ('"minikube kubectl -- get pods --namespace default -o jsonpath={.items[1].metadata.name}"') do set FRONTEND_POD_NAME=%i
734 | minikube kubectl -- --namespace default port-forward %FRONTEND_POD_NAME% 30444:80
735 | ```
736 |
737 | Now you can access your application at `http://localhost:30444/`
738 |
739 | ### Congratulations! 🎉
740 |
741 | You have now created a Helm Chart which deploys a frontend and a backend whilst also calling and installing a dependency chart from the internet.
742 |
743 | ### Interacting with the application
744 |
745 | By visiting `http://localhost:30444/` you should be able to see the UI as shown on the image below
746 |
747 | 
748 |
749 | Lets add an item by filling the textbox and clicking on the `Add new toDo` button
750 |
751 | 
752 |
753 | By clicking the `Add new ToDo` button, the item should be added on the list as shown on image below.
754 |
755 | You should also be able to see below message on the backend service
756 |
757 | `Creating Todo for NodeConfEU Clean the bicycle`
758 |
759 | by viewing the logs with below command
760 | ```sh
761 | kubectl logs backend-deployment-xxxxxxxx-xxxxx -f
762 | ```
763 |
764 | 
765 |
766 | Click the x button next to the todo item and it should be removed
767 |
768 | ### Uninstalling the App
769 |
770 | Once you are finished you can uninstall the chart by running:
771 |
772 | ```sh
773 | $ helm uninstall myapp
774 | ```
--------------------------------------------------------------------------------
/helm/images/frontend-add-item.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/helm/images/frontend-add-item.png
--------------------------------------------------------------------------------
/helm/images/frontend-added-item.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/helm/images/frontend-added-item.png
--------------------------------------------------------------------------------
/helm/images/frontend-initial-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nodeshift/tutorial/b52a7c68e1a20cce38cbbbae11f80f87506dd3cc/helm/images/frontend-initial-ui.png
--------------------------------------------------------------------------------