-
Notifications
You must be signed in to change notification settings - Fork 0
/
Theory.txt
89 lines (69 loc) · 9.27 KB
/
Theory.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
**AIM: To set up a workflow, to initialize an express server inside a docker container, instead of a local system.
**WORKFLOW
--> Set up a custom docker image
--> Migrate all the source into Default Docker Node image and base this on our custom image.
--> Install all the dependencies required for the server (like express) in the custom image --> The final product will have our express server running independently inside a docker container.
**KEY POINTS
--> Docker image is build in layers --> every command inside a Docker file is treated as a separate layer. --> Docker caches the result of every layer after executing it.
#Inside the Docker file -->
FROM node:19 //pulls node:19 image from Docker hub
WORKDIR /app //sets the working directory to /app of Node:19 image
COPY package.json . //copies package.json to the current working dir. ie., /app
RUN npm install //installs all the dependencies --> build time cmd
COPY . ./ //copies all the source code files (including already copied package.json) of the root directory of the Dockerfile, into the workdir of the image, ie, /app, expect the file specified in .dockerignore file
EXPOSE 3000 //exposing or listening to port 3000 (But this line is basically for documentation purposes & doesn't actully connect the server to port 3000 on its own)
CMD ["node", "index.js"] //running "node index.js" command in runtime
Now note --> Although we are copying each file of the source directory in the working directory at the last layer, including package.json --> yet we are copying it in a separate layer (layer no. 3) --> this is a step of Micro-optimization -->
While building an application in a practical scenario, we seldom change the dependencies of the package.json file during feature addiing or other fixes. --> Docker caches all the layers during its executing --> Hence if we are caching the package.json layer separately then, that means we do not need to install all the dpendencies once again at each docker build (since, if one layer is rebuild due to changes not found in caches, all layers below it are re-executed) -->
Hence caching the package.json in a separate layer, which is seldom changed in production, the dependencies layer need not be reinstalled --> THUS SPEEDING UP DOCKER BUILD PROCESS --> Changes happen only at the last layer during fixing a bbug in production.
NB: Inside docker file --> Specifying env variables -->
ENV PORT 3000
EXPOSE $PORT
ARG NODE_ENV --> Argument passed in RUN command
RUN if [ "$NODE_ENV" = "development" ]; \
then npm install; \
else npm install --only=production; \
fi
--> This statement checks if it is development environment. If yes then it runs npm install along with Dev Dependencies(nodemon), otherwise, ie, in production it install everything ecept dev dependencies (like nodemon).
**DOCKER CMDs
--> docker build -t <name> . --> building a docker image by providing reference (path) to the docker file (here the current directory or .). -t flag specifies the name of a docker image.
--> docker run -v{pwd}:/app:ro -v /app/node_modules -p 3000:3000 -d --name <container-name> <image-name>
--> Runs a docker image inside a container with name as <container-name>, in detached mode (so that command line is still active, instead of docker-cli). -p 3000:3000 signifies that, any traffic coming to port 3000 of our local machine(:3000), is forwarded to the node container build at port 3000 of the machine(3000:) [-p <port of container>:<port from where traffic comees into the container>] --> Without the port flag, due to security restrictions of the docker containers, the container will not be able to interact with the local system outside the container.
-v ${pwd}:/app:ro --> passing a bind-mount volume to sync the local folders of the present working directory(pwd) to the working directory of the docker image (/app) --> So that whenever a change is made in the source code, those changes are automatically relayed to the docker container, without having to rebuild the image. :ro specifies the Ops inside docker container to happen in "read-only" mode, i.e., no file can be added or deleted from the local system using a docker container. Nevertheless, files can be added or deleted from the container via local system using bind mount.
-v /app/node_modules --> this volume specifies the bind-mount vol. to not overwrite the node_modules folder inside docker container, so that even if it is removed from the source file, it is not removed from docker container & hence doesn't crash the system.
--> docker exec -it <container-name> bash --> cd into the file system of docker container
--> docker run -v ${pwd}:/app:ro -v /app/node_modules --env PORT=4000 -p 3000:4000 -d --name express-server devops-docker --> running a docker container "express-server" by passing an env variable.
--> docker run -v ${pwd}:/app:ro -v /app/node_modules --env PORT=4000 -p 3000:4000 -d --name express-server devops-docker --> running a docker container "express-server" by passing .env file.
|
|
---> *All these run cmds can be composed into a docker compose file.
--> docker-compose up --> composes a docker container.
--> docker-compose up -d --build --> composes a docker container in detached mode with a forced build command, even if the image already exists.
--> docker-compose down --> deletes a docker container.
--> docker-compose down -v --> deletes a docker container along with all the unnecessay/anonymous volumes created during container build.
--> docker exec -it <mongo-image-name> -u "<mongo-username>" -p "<mongo-password>" --> enters mongo-cli.
--> docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d express-server --> Spins up the express-server image. But if it depends on any other image (here, depends_on: - mongo) --> then it will spin up that container first.
--> docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d --no-deps express-server --> Spins up the express-server image without spinning up its dependencies. Even if it depends on any other image (here, depends_on: - mongo) --> it will not spin up that container first.
--> docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d --scale express-server=2 --> Scales up the service to create 2 node instances.
--> docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build -V --> -V flag discards all anonymous volumes that are previously up & running, & builds up new volumes with newly installed dependencies (if any).
--> docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d --force-recreate --no-deps express-server --> forcibly rebuilds a specific image without rebuilding its dependency images.
--> docker-compose -f docker-compose.yml -f docker-compose.prod.yml build --> build only for custom images.
**SCALING WITH NGINX
--> To scale the application, we introduce multiple node instances/to connect to our single MongoDB.
--> These instances are connected using a load-balancer - NGINX.
--> We receive request from user at any port on our local machine (like 3000).
--> This request if forwarded to port 80: default port of NGINX.
--> This inccoming request is relayed to a node instance on the port the server is listening on --> the request is then forwarded to MongoDb port(27017).
**PRODUCTION WORKFLOW TO TRACK CODE CHANGES
--> Changes made on local dev server & build the changes --> push newly built node to DOCKERHUB --> pull new node images in remote server --> Run Docker Compose up & rebuild just the server.
--> docker-compose -f docker-compose.yml -f docker-compose.prod.yml push express-server --> push changes in image "express-server" to DockerHub.
--> Pulling & rebuilding images at server side automatically (automation) --> *Docker Watchtower* --> It is a Docker container that handles the automation of other containers.
--> docker run -d --name watchtower -e WATCHTOWER_TRACE=true -e WATCHTOWER_DEBUG=true -e WATCHTOWER_POLL_INTERVAL=50 -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower app-express-server-1 --> creates a watch tower to automatically check for changes after each 50s, in app-express-server-1 container & pull the changes if any.
--> Container Orchestrator : *Docker Swarm* --> While spinning down an image or spinning up an image at the server using docker-compose, We may experience a service crash or outage for heavy applications --> This is essentially handled by container orchestrators (like kubernetes, docker swarm) --> Then don't spin down the whole working server containers, while making changes in the new containers. --> In addition, unlike docker-compose, docker swarm can orchestrate multiple servers/node-instances, each of which are known "node".
--> A docker swarm node is of 2 types :
1. Manager Node - handles the orchestration of the containers.
2. Worker Node - carries out operations assigned to it by manager nodes.
[NB: A manager node is simultaneously both manager & worker node, by default]
--> A docker swarm while spinning up new updated containers, doesn't spin down the old ones, hence preventing outage of service during continuous deployment of updates (ROLLING UPDATES).
--> docker stack deploy -c docker-compose.yml -c docker-compose.prod.yml express_server --> deploys the changes made to containers via swarm in a stack (here = "express_server").
--> docker stack services express_server --> equivalent to docker ps --> lists out all running services of a stack