diff --git a/Docker.md b/Docker.md new file mode 100644 index 0000000000000000000000000000000000000000..099be0e59db9c7b68564d9a534e91d23c6d03e31 --- /dev/null +++ b/Docker.md @@ -0,0 +1,112 @@ +# Docker from Zero to GigaChad + +Docker ça faire presque tous ce qu'on veut avec conteneurs. + +## Écrire un Dockerfile + +Un Dockerfile c'est une _recette_ pour construire une image Docker et ça ressemble à ça: +```Dockerfile +FROM python:3.8 + +COPY requirements.txt . +RUN pip install -r requirements.txt + +COPY main.py main.py + +ENTRYPOINT ["python", "main.py"] +``` +On reconnait **3** parties essentielles: + +### L'image de base +```Dockerfile +FROM python:3.8 +``` +C'est une image de laquelle on s'inspire pour créer la notre, ça nous évite d'avoir à installer python nous même par exemple. + +### Un ensemble d'instructions +```Dockerfile +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY main.py main.py +``` +Ces instructions s'exécutent lors de la construction de l'image. + +Les instructions les plus courantes sont: +Pour ajouter des fichiers dans l'image: +```Dockerfile +COPY <un fichier/dossier sur mon pc> <un emplacement dans mon image> +COPY --chown=user:group <mon fichier> <ma destination> +``` + +Pour lancer une commande dans l'image: +```Dockerfile +RUN ma commande +``` + +### Un entrypoint +C'est la commande qui s'exécutent lorsque le conteneur est démarre à partir de l'image. +Deux façons possible de l'écrire: +exec form: + +```Dockerfile +ENTRYPOINT ["mon executable", "arg1", "arg2"] +``` +C'est la façon préféré de faire. + +shell form: +```Dockerfile +ENTRYPOINT ma commande 1 && ma commande 2 +``` +nécessaire lorsque l'on a plusieurs commande à faire tourner de façon séquentielle. + +## Construire une image à partir d'un Dockerfile +Une image se construit à partir d'un Dockerfile et d'un contexte (c'est un dossier qui correspond au **.** que tu as mis dans to Dockerfile). Souvent le contexte c'est `.` et il n'y a pas besoin de spécifier l'emplacement du Dockerfile, si il s'appelle `Dockefile` et est à la racine du contexte. Pour constuire l'image on utilise alors: +```bash +docker build . -t <le nom de mon image>:<tag> +``` +Le tag sert à spécifier plusieurs version de la même image, un peu comme des branches sur git. +## Push une image sur un registry +Un registry c'est juste un espace de stockage disponible pour héberger des images Docker. +```bash +# On se log au registry (les identifiants sont sur le bitwarden) +docker login registry.viarezo.fr +# On build l'image avec le nom du registry dedans +docker build . -t registry.viarezo.fr/<nom du projet>/<image>:<tag> +# On push l'image sur le registry +docker push registry.viarezo.fr/<nom du projet>/<image>:<tag> +``` +Attention il faut avoir créé le projet au préalable sur le registry, en cochant bien l'option public. + +## Faire tourner un conteneur à partir d'une image +Une fois qu'on a construit notre image, on peut l'utiliser pour créer autant de conteneurs que l'on veut: +```bash +docker run <image> +# En forwardant un port +docker run -p <port sur mon pc>:<port dans le conteneur> <image> +# En ajoutant des variables d'environnement +docker run -e FOO=BAR <image> +# Avec un .env +docker run --env-file .env <image> +``` + +## Gérer les conteneurs qui s'exécutent +```bash +# Voir les conteneurs qui tournent et les informations associées +docker ps +# Stopper un conteneur +docker stop <conteneur> +# Redémarrer un conteneur stoppé +docker start <conteneur> +# Supprimer un conteneur stoppé +docker rm <conteneur> +``` + +## Débugger un conteneur +```bash +# Voir les logs +docker logs <conteneur> +# Executer une commande dans un conteneur +docker exec -it <conteneur> <commande> +# Récupérer un shell dans un conteneur (attention bash ne marche pas pour les images alpine) +docker exec -it <conteneur> sh/bash +``` \ No newline at end of file diff --git a/README.md b/README.md index c423458dd3bb91c296e1bf536032026fecef63c5..717a2c09528c018ea6d9ba68127e609b5a66bf6f 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,8 @@ Cela veut dire que si vous ne spécifiez pas explicitement de namespace lorsque Et cette application c'est VRoum. -Pour cette première partie, l'objectif est de construire les images qui vont faire tourner notre site, il y a deux images à vous répartir (ou à faire ensemble séquentiellement). +Pour cette première partie, l'objectif est de construire les images qui vont faire tourner notre site, il y a deux images à vous répartir (ou à faire ensemble séquentiellement). +Pour un petit pense-bête sur Docker, n'hésite pas à lire [cette petite fiche](./Docker.md) ### Le Front @@ -94,214 +95,6 @@ Vérifie que ce n'est pas le cas ou alors fait les changements nécéssaires. On préfère souvent utiliser une image alpine à une image debian en production, car celle-ci sont beaucoup plus légère. Néanmoins la gestion des dépendences, est souvent plus difficile. Utilise une image alpine (python:3.8-alpine) comme base pour ton image. - -## 1. (Optional) Build and launch the app locally - -> This task is optional, don't loose time on it right now! - -### Why - -If you truly want to immerse yourself in the life of a developer, you will need to be able to iterate quickly on the app locally. - -### What - -Be creative, try to modify a simple thing in the app. - -### How - -For this you simply need the `go` cli installed and some knownledge of this language. - -When you are happy with the result, you can launch the app with `go run main.go`, or build a binary with `go build`. - -### Checks - -- [ ] I can run the app locally, and see the web UI. -- [ ] I have implemented a small change in the application and it still runs - -## 2. Build a container image (Docker) - -### Why - -While you build and iterate on your app locally, you need to be able to deploy it on a real production environment. - -Since you don't know where it will run (isolated virtual machine, which packages are installed), we want to ensure the reproductability and isolation of the application. That's why containers, that `docker` helps build and run, are made for! - -It is a standard API to build and ship applications across diverse workloads. Whatever the server it is running on, you _image_ should always construct the same isolated environment. - -Moreover, it is way less expensive in resources (CPU, RAM) than a Virtual Machine, which acheives an isolation by reinstalling a whole OS. - -### What - -We need to _build_ a container _image_ from the code in this repository. For this, the command `docker build -t <image-name>:<version> .` builds an image from a local _recipe_ `Dockerfile`. - -For example, for a simple python application, it could be: - -```Dockerfile -FROM python:3.8 - -COPY requirements.txt . -RUN pip install -r requirements.txt - -COPY main.py main.py - -CMD ["python", "main.py"] -``` - -You can find the complete [Dockerfile reference](https://docs.docker.com/engine/reference/builder/) here. - -Here we have a _webservice_ written in _golang_, running an HTTP server on the port `3000`. -It serves some static files (stored in `/public`), for the UI. You will mainly access it through a `GET /` for the UI, but there are other routes to manage the state of the app. - -### How - -You can follow such [a tutorial](https://docs.docker.com/language/golang/build-images/) - -1. Write a `Dockerfile`. You need to start from a _base _image_, ideally with golang already install. -2. In the `Dockerfile`, download the microservice's dependencies. Since latest golang version, we only need `go.mod` and `go.sum` for this task. -3. In the `Dockerfile`, build the microservice. You need the command `go build` for this. -4. In the `Dockerfile`, add the `public` folder inside the container, in the same `public` folder. - - ```Dockerfile - COPY ./public public - ``` - -4. When the container starts, run the microservice. -5. Build a container image: `docker build -t guestbook:v0.1.0 .` -6. Run the container. - - You need to _expose_ the port of your application, which run on `3000`. For this, you just need to add the `--publish <external-port>:<internal-port>` to the `docker run` command. - - ```bash - docker run --publish 3000:3000 guestbook:v0.1.0 - ``` - -7. Check that the microservice responds to requests on - http://localhost:3000. You should see the following UI: - -  - -8. **Optional**: Implement some best practices, such as "multi-stage builds". It help reduce the size of your images, and increase security. - - The image you built so far is pretty large because it contains the entire Go - toolkit. It's time to make it smaller. Much smaller. Here is what you need to - do: - - 1. Check to see how big your container image is. - 2. Change the `go build` command to make the binary statically linked (if you - don't know what that means, just ask!). - 3. In your `Dockerfile`, create a second stage that starts from `scratch`. - 4. Copy the binary from the first stage to the second. - 5. In the second stage, run the microservice. - 6. Build your container image again. - 7. Check to see how big the image is now. - - - -### Checks - -- [ ] I can build an image locally -- [ ] I can run a the container locally -- [ ] I can access the web interface locally - -<details> -<summary><em>Compare your work to the solution before moving on. Are there differences? Is your approach better or worse? Why?</em></summary> - -You can find the complete solution [here](https://github.com/padok-team/dojo-guestbook/blob/feat/solution/Dockerfile). Don't spoil yourself too much! - -</details> - -## 3. Run it locally with docker-compose - -### Why - -You have a working local environment, however you already need to chain a few commands, and as your app will be growing more complex, the setup will be harder to maintain. - -Instead of having to type an _imperative_ chain of commands, you can have a _declarative_ description of your local _docker/container_ application. That's is why `docker-compose` is made for: it reads this config and run the right `docker commands` for you. - -### What - -We need to be able to launch the current container with only the `docker-compose up` command. - -The `docker-compose.yaml` file will contains everything needed: - -- how to build the image -- how to run the container, including configuration of port -- how to link it to another container -- how to persistent storage - -### How - -There is a [_get started_](https://docs.docker.com/compose/gettingstarted/) article, or the [complete specification](https://docs.docker.com/compose/compose-file/) - -- define your guestbook service -- you can use the image you built, but you can also specify how to rebuild it! -- don't forget to expose the port needed for your application - -### Checks - -- [ ] I can launch locally the application with `docker-compose up` -- [ ] I can see the UI in my brower at `http://localhost:3000` - -<details> -<summary>Compare your work to the solution before moving on. Are there differences? Is your approach better or worse? Why?</summary> - -You should have something like: - -```yaml -version: '3' -services: - guestbook: - build: - context: ./ - dockerfile: Dockerfile - ports: - - 3000:3000 -``` - -</details> - - -## 4. Add a database to your service - -### Why - -If you test your app, you can see a big **⚠️ No database connection... ⚠️**. Furthermore, when you try to add something to the guestbook, it hangs (⌛) without saving it (try to refresh the page). - -The application is actually stateless, and needs a Redis backend to save its state. To avoid interfering with your local installation, we will run it in container, using once again `docker` and `docker-compose`. - -### What - -We simply need to add a new service in our docker-compose file, and have a way for the app to use it. - -### How - -1. Add a `redis` service in your app. Don't build redis locally, but use the public `redis:6` image. -2. Expose its redis port `6379`. -3. Make the guestbook app use it: - - The Guestbook app uses _environment variable_ for its configuration. Here you need to setup the `REDIS_HOST` variable to the hostname of your redis cluster. In a docker-compose environment, each service can be called with its name. -4. Try to run it: does the application store the state? -5. (Optional) Make it persistent! - - Currently, if you save some sentences in the app, then run `docker-compose down` and `docker-compose up` again, you'll see that you will loose all your data! 😢 - - You can manage volumes in docker-compose, which are persisted, and mount these volumes in your app. If you prefer, you can also link a local folder to a container, it can be useful for live reloading. - -### Check - -- [ ] The application actually saves messages - -  - -- [ ] (Optional) If you run `docker-compose down`, you don't loose data when you relaunch the app. - -<details> -<summary><em>Compare your work to the solution before moving on. Are there differences? Is your approach better or worse? Why?</em></summary> - -You can find the complete solution [here](https://github.com/padok-team/dojo-guestbook/blob/feat/solution/docker-compose.yml). Don't spoil yourself too much! - -</details> - ## 5. Deploy you app on Kubernetes: the Pod > If you are here, ask for a quick formation on Kubernetes. We will make a quick overview for everyone!