Skip to content

Commit

Permalink
Sync with original text (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
forresst authored Jan 13, 2021
1 parent 9470408 commit be6bf84
Show file tree
Hide file tree
Showing 45 changed files with 1,945 additions and 180 deletions.
2 changes: 1 addition & 1 deletion .operations/writing-guidelines.french.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ En plus d'être d'une grande fiabilité et d'une grande qualité rédactionnelle

## 4. Formatage cohérent

Le contenu est présenté à l'aide de modèles prédéfinis. Tout contenu futur doit être conforme au même modèle. Si vous souhaitez ajouter de nouveaux points, copiez le format d'un point existant et complétez-le selon vos besoins. Pour plus d'informations, veuillez consulter [ce modèle](https://github.com/i0natan/nodebestpractices/blob/master/sections/template.md).
Le contenu est présenté à l'aide de modèles prédéfinis. Tout contenu futur doit être conforme au même modèle. Si vous souhaitez ajouter de nouveaux points, copiez le format d'un point existant et complétez-le selon vos besoins. Pour plus d'informations, veuillez consulter [ce modèle](https://github.com/goldbergyoni/nodebestpractices/blob/master/sections/template.md).

## 5. C'est à propos de Node.js

Expand Down
555 changes: 479 additions & 76 deletions README.french.md

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions sections/docker/avoid-build-time-secrets.french.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Clean build-time secrets, avoid secrets as args

<br/><br/>

### One Paragraph Explainer


A Docker image isn't just a bunch of files but rather multiple layers revealing what happened during build-time. In a very common scenario, developers need the npm token during build time (mostly for private registries) - this is falsely achieved by passing the token as a build time args. It might seem innocent and safe, however this token can now be fetched from the developer's machine Docker history, from the Docker registry and the CI. An attacker who gets access to that token is now capable of writing into the organization private npm registry. There are two more secured alternatives: The flawless one is using Docker --secret feature (experimental as of July 2020) which allows mounting a file during build time only. The second approach is using multi-stage build with args, building and then copying only the necessary files to production. The last technique will not ship the secrets with the images but will appear in the local Docker history - This is typically considered as secured enough for most organizations.

<br/><br/>

### Code Example – Using Docker mounted secrets (experimental but stable)

<details>

<summary><strong>Dockerfile</strong></summary>

```
# syntax = docker/dockerfile:1.0-experimental
FROM node:12-slim
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npm,target=/root/.npmrc npm ci
# The rest comes here
```

</details>

<br/><br/>

### Code Example – Building securely using multi-stage build

<details>

<summary><strong>Dockerfile</strong></summary>

```
FROM node:12-slim AS build
ARG NPM_TOKEN
WORKDIR /usr/src/app
COPY . /dist
RUN echo "//registry.npmjs.org/:\_authToken=\$NPM_TOKEN" > .npmrc && \
npm ci --production && \
rm -f .npmrc
FROM build as prod
COPY --from=build /dist /dist
CMD ["node","index.js"]
# The ARG and .npmrc won't appear in the final image but can be found in the Docker daemon un-tagged images list - make sure to delete those
```

</details>

<br/><br/>

### Code Example Anti Pattern – Using build time args

<details>

<summary><strong>Dockerfile</strong></summary>

```
FROM node:12-slim
ARG NPM_TOKEN
WORKDIR /usr/src/app
COPY . /dist
RUN echo "//registry.npmjs.org/:\_authToken=\$NPM_TOKEN" > .npmrc && \
npm ci --production && \
rm -f .npmrc
# Deleting the .npmrc within the same copy command will not save it inside the layer, however it can be found in image history
CMD ["node","index.js"]
```

</details>

<br/><br/>

### Blog Quote: "These secrets aren’t saved in the final Docker"

From the blog, [Alexandra Ulsh](https://www.alexandraulsh.com/2019/02/24/docker-build-secrets-and-npmrc/?fbclid=IwAR0EAr1nr4_QiGzlNQcQKkd9rem19an9atJRO_8-n7oOZXwprToFQ53Y0KQ)

> In November 2018 Docker 18.09 introduced a new --secret flag for docker build. This allows us to pass secrets from a file to our Docker builds. These secrets aren’t saved in the final Docker image, any intermediate images, or the image commit history. With build secrets, you can now securely build Docker images with private npm packages without build arguments and multi-stage builds.
```
```
85 changes: 85 additions & 0 deletions sections/docker/bootstrap-using-node.french.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Bootstrap container using node command instead of npm

## One paragraph explainer

We are used to see code examples where folks start their app using `CMD 'npm start'`. This is a bad practice. The `npm` binary will not forward signals to your app which prevents graceful shutdown (see [/sections/docker/graceful-shutdown.md]). If you are using Child-processes they won’t be cleaned up correctly in case of unexpected shutdown, leaving zombie processes on your host. `npm start` also results in having an extra process for no benefit. To start you app use `CMD ['node','server.js']`. If your app spawns child-processes also use `TINI` as an entrypoint.

### Code example - Bootsraping using Node

```dockerfile

FROM node:12-slim AS build


WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm ci --production && npm clean cache --force

CMD ["node", "server.js"]
```


### Code example - Using Tiny as entrypoint

```dockerfile

FROM node:12-slim AS build

# Add Tini if using child-processes
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini

WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm ci --production && npm clean cache --force

ENTRYPOINT ["/tini", "--"]

CMD ["node", "server.js"]
```

### Antipatterns

Using npm start
```dockerfile

FROM node:12-slim AS build
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm ci --production && npm clean cache --force

# don’t do that!
CMD "npm start"
```

Using node in a single string will start a bash/ash shell process to execute your command. That is almost the same as using `npm`

```dockerfile

FROM node:12-slim AS build
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm ci --production && npm clean cache --force

# don’t do that, it will start bash
CMD "node server.js"
```

Starting with npm, here’s the process tree:
```
$ ps falx
UID PID PPID COMMAND
0 1 0 npm
0 16 1 sh -c node server.js
0 17 16 \_ node server.js
```
There is no advantage to those two extra process.

Sources:


https://maximorlov.com/process-signals-inside-docker-containers/


https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#handling-kernel-signals
27 changes: 27 additions & 0 deletions sections/docker/clean-cache.french.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Clean NODE_MODULE cache

<br/><br/>

### One Paragraph Explainer

Node package managers, npm & Yarn, cache the installed packages locally so that future projects which need the same libraries won't need to fetch from a remote repository. Although this duplicates the packages and consumes more storage - it pays off in a local development environment that typically keeps installing the same packages. In a Docker container this storage increase is worthless since it installs the dependency only once. By removing this cache, using a single line of code, tens of MB are shaved from the image. While doing so, ensure that it doesn't exit with non-zero code and fail the CI build because of caching issues - This can be avoided by including the --force flag.

*Please not that this is not relevant if you are using a multi-stage build as long as you don't install new packages in the last stage*

<br/><br/>

### Code Example – Clean cache

<details>
<summary><strong>Dockerfile</strong></summary>

```
FROM node:12-slim AS build
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm ci --production && npm cache clean --force
# The rest comes here
```

</details>
49 changes: 49 additions & 0 deletions sections/docker/docker-ignore.french.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Use .dockerignore to prevent leaking secrets

<br/><br/>

### One Paragraph Explainer

The Docker build command copies the local files into the build context environment over a virtual network. Be careful - development and CI folders contain secrets like .npmrc, .aws, .env files and other sensitive files. Consequently, Docker images might hold secrets and expose them in unsafe territories (e.g. Docker repository, partners servers). In a better world the Dockerfile should be explicit about what is being copied. On top of this include a .dockerignore file that acts as the last safety net that filters out unnecessary folders and potential secrets. Doing so also boosts the build speed - By leaving out common development folders that have no use in production (e.g. .git, test results, IDE configuration), the builder can better utilize the cache and achieve better performance

<br/><br/>

### Code Example – A good default .dockerignore for Node.js

<details>
<summary><strong>.dockerignore</strong></summary>

```
**/node_modules/
**/.git
**/README.md
**/LICENSE
**/.vscode
**/npm-debug.log
**/coverage
**/.env
**/.editorconfig
**/.aws
**/dist
```

</details>

<br/><br/>

### Code Example Anti-Pattern – Recursive copy of all files

<details>
<summary><strong>Dockerfile</strong></summary>

```
FROM node:12-slim AS build
WORKDIR /usr/src/app
# The next line copies everything
COPY . .
# The rest comes here
```

</details>
29 changes: 29 additions & 0 deletions sections/docker/generic-tips.french.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[]: ../../assets/images/checkbox-small-blue.png

# Common Node.js Docker best practices

This common Docker guidelines section contains best practices that are standardized among all programming languages and have no special Node.js interpretation

## ![] Prefer COPY over ADD command

**TL;DR:** COPY is safer as it copies local files only while ADD supports fancier fetches like downloading binaries from remote sites

## ![] Avoid updating the base OS

**TL;DR:** Updating the local binaries during build (e.g. apt-get update) creates inconsistent images every time it runs and also demands elevated privileges. Instead use base images that are updated frequently

## ![] Classify images using labels

**TL;DR:** Providing metadata for each image might help Ops professionals treat it adequately. For example, include the maintainer name, build date and other information that might prove useful when someone needs to reason about an image

## ![] Use unprivileged containers

**TL;DR:** Privileged container have the same permissions and capabilities as the root user over the host machine. This is rarely needed and as a rule of thumb one should use the 'node' user that is created within official Node images

## ![] Inspect and verify the final result

**TL;DR:** Sometimes it's easy to overlook side effects in the build process like leaked secrets or unnecessary files. Inspecting the produced image using tools like [Dive](https://github.com/wagoodman/dive) can easily help to identify such issues

## ![] Perform integrity check

**TL;DR:** While pulling base or final images, the network might be mislead and redirected to download malicious images. Nothing in the standard Docker protocol prevents this unless signing and verifying the content. [Docker Notary](https://docs.docker.com/notary/getting_started/) is one of the tools to achieve this
84 changes: 84 additions & 0 deletions sections/docker/graceful-shutdown.french.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Shutdown gracefully

<br/><br/>

### One Paragraph Explainer

In a Dockerized runtime like Kubernetes, containers are born and die frequently. This happens not only when errors are thrown but also for good reasons like relocating containers, replacing them with a newer version and more. It's achieved by sending a notice (SIGTERM signal) to the process with a 30 second grace period. This puts a challenge on the developer to ensure the app is handling the ongoing requests and clean-up resources in a timely fashion. Otherwise thousands of sad users will not get a response. Implementation-wise, the shutdown code should wait until all ongoing requests are flushed out and then clean-up resources. Easier said than done, practically it demands orchestrating several parts: Tell the LoadBalancer that the app is not ready to serve more requests (via health-check), wait for existing requests to be done, avoid handling new requests, clean-up resources and finally log some useful information before dying. If Keep-Alive connections are being used, the clients must also be notified that a new connection should be established - A library like [Stoppable](https://github.com/hunterloftis/stoppable) can greatly help achieving this.

<br/><br/>


### Code Example – Placing Node.js as the root process allows passing signals to the code (see [bootstrap using node](/sections/docker/bootstrap-using-node.md))

<details>

<summary><strong>Dockerfile</strong></summary>

```
FROM node:12-slim
# Build logic comes here
CMD ["node", "index.js"]
#This line above will make Node.js the root process (PID1)
```

</details>

<br/><br/>

### Code Example – Using Tiny process manager to forward signals to Node

<details>

<summary><strong>Dockerfile</strong></summary>

```
FROM node:12-slim
# Build logic comes here
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]
CMD ["node", "index.js"]
#Now Node will run a sub-process of TINI which acts as PID1
```

</details>

<br/><br/>

### Code Example Anti Pattern – Using npm scripts to initialize the process

<details>

<summary><strong>Dockerfile</strong></summary>

```
FROM node:12-slim
# Build logic comes here
CMD ["npm", "start"]
#Now Node will run a sub-process of npm and won't receive signals
```

</details>

<br/><br/>

### Example - The shutdown phases

From the blog, [Rising Stack](https://blog.risingstack.com/graceful-shutdown-node-js-kubernetes/)

![alt text](/assets/images/Kubernetes-graceful-shutdown-flowchart.png "The shutdown phases")
Loading

0 comments on commit be6bf84

Please sign in to comment.