Compare commits

...

14 Commits

Author SHA1 Message Date
Nicolas Massé c91493ae83 document the Markdown syntax of the content 1 month ago
Nicolas Massé a65d846135 fix some HTML errors 1 month ago
Nicolas Massé 231831ebd7 serve the images as WebP in several widths 1 month ago
Nicolas Massé 7fe8344056 replace the custom shortcodes with Markdown and render hooks 1 month ago
Nicolas Massé 2a29098570 use the same spelling for the Keycloak term 1 month ago
Nicolas Massé e67dbba625 search: upgrade to Fuse.js 7.5.0 and load the index from a JSON file 1 month ago
Nicolas Massé c7d6ad75bd add a description to the sections 1 month ago
Nicolas Massé 5001ac8f05 improve the descriptions, social images and structured data 1 month ago
Nicolas Massé 3c9a0ecdb9 embed YouTube videos from youtube-nocookie.com 1 month ago
Nicolas Massé 8e5c14917e remove the stray whitespace around internal links 1 month ago
Nicolas Massé 159cbc4022 fix the clip path of the british flag 1 month ago
Nicolas Massé 6a66776392 add security and cache headers 1 month ago
Nicolas Massé 0018fe4d79 declare the sitemap in robots.txt 1 month ago
Nicolas Massé 35c8084b76 serve the fonts in woff2 and only preload the ones used on every page 1 month ago
  1. 37
      README.md
  2. 8
      assets/css/itix.css
  3. 32
      config.yaml
  4. 8
      content/english/_index.md
  5. 4
      content/english/blog/3scale-toolbox-anonymous-policy.md
  6. 8
      content/english/blog/3scale-toolbox-url-rewriting-policy.md
  7. 1
      content/english/blog/_index.md
  8. 2
      content/english/blog/airgap-openshift-installation-move-registry-created-using-oc-adm-release-mirror-between-environments/index.md
  9. 28
      content/english/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md
  10. 8
      content/english/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md
  11. 28
      content/english/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-and-qemu/index.md
  12. 34
      content/english/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-aws/index.md
  13. 2
      content/english/blog/configure-redhat-sso-3scale-cli/index.md
  14. 12
      content/english/blog/consistent-dns-name-resolution-for-virtual-machines-and-containers/index.md
  15. 8
      content/english/blog/deploy-apis-from-your-cicd-pipeline-with-the-3scale-rest-api.md
  16. 14
      content/english/blog/deploy-openshift-single-node-in-kvm/index.md
  17. 12
      content/english/blog/enable-global-policies-apicast.md
  18. 28
      content/english/blog/homelab-server-2u-short-depth-front-io-ampere-altra-arm64-architecture/index.md
  19. 22
      content/english/blog/how-to-run-performance-tests-with-k6-prometheus-grafana/index.md
  20. 2
      content/english/blog/install-openwrt-raspberry-pi/index.md
  21. 12
      content/english/blog/jmeter-assess-software-performances/index.md
  22. 2
      content/english/blog/nginx-with-tls-on-openwrt/index.md
  23. 6
      content/english/blog/use-ansible-to-manage-the-qos-of-your-openshift-workload/index.md
  24. 22
      content/english/blog/use-google-account-openid-connect-provider/index.md
  25. 64
      content/english/blog/use-qlkube-to-query-the-kubernetes-api.md
  26. 8
      content/english/blog/writing-workshop-instructions-with-hugo-deploy-openshift/index.md
  27. 2
      content/english/blog/writing-workshop-instructions-with-hugo/index.md
  28. 1
      content/english/opensource/_index.md
  29. 4
      content/english/search/index.md
  30. 1
      content/english/speaking/_index.md
  31. 4
      content/english/speaking/apidays-2017/index.md
  32. 12
      content/english/speaking/devoteam-tech-for-people-2018/index.md
  33. 21
      content/english/speaking/devoxx-france-2019/index.md
  34. 2
      content/english/speaking/red-hat-forum-2017/index.md
  35. 10
      content/english/speaking/red-hat-open-demo-build-multi-architecture-ci-cd-pipelines-to-run-your-apps-in-the-cloud-and-at-the-edge/index.md
  36. 10
      content/english/speaking/red-hat-open-demo-from-the-esp8266-to-the-openshift-cluster-discover-what-red-hat-can-offer-for-the-edge-computing/index.md
  37. 14
      content/english/speaking/red-hat-open-demo-mission-impossible-stop-the-crazy-train-with-ai-and-edge-before-it-is-too-late/index.md
  38. 26
      content/english/speaking/red-hat-summit-connect-france-2024/index.md
  39. 3
      content/english/speaking/red-hat-tech-exchange-2018/index.md
  40. 3
      content/english/speaking/red-hat-tech-exchange-2019/index.md
  41. 2
      content/english/speaking/red-hat-tech-exchange-2023/index.md
  42. 2
      content/english/speaking/techweek-sg-2019/index.md
  43. 1
      content/english/topics/_index.md
  44. 1
      content/english/writing/_index.md
  45. 2
      content/english/writing/apis-as-a-product-get-started-in-no-time.md
  46. 2
      content/english/writing/how-size-your-projects-red-hats-single-sign-technology.md
  47. 2
      content/english/writing/red-hat-single-sign-on-give-it-a-try-for-no-cost.md
  48. 2
      content/english/writing/using-a-public-certificate-with-red-hat-single-sign-on-keycloak.md
  49. 8
      content/french/_index.md
  50. 1
      content/french/blog/_index.md
  51. 28
      content/french/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md
  52. 8
      content/french/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md
  53. 34
      content/french/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-aws/index.md
  54. 12
      content/french/blog/connect-podman-container-to-openvswitch/index.md
  55. 26
      content/french/blog/decouverte-de-coreos/index.md
  56. 48
      content/french/blog/fibre-orange-remplacer-livebox-routeur-centos-stream-8/index.md
  57. 28
      content/french/blog/homelab-server-2u-short-depth-front-io-ampere-altra-arm64-architecture/index.md
  58. 14
      content/french/blog/installation-mise-jour-openshift-environnement-deconnecte/index.md
  59. 1
      content/french/opensource/_index.md
  60. 4
      content/french/search/index.md
  61. 1
      content/french/speaking/_index.md
  62. 2
      content/french/speaking/demobox-redhat-sso/index.md
  63. 21
      content/french/speaking/devoxx-france-2019/index.md
  64. 8
      content/french/speaking/devoxx-france-2025/index.md
  65. 8
      content/french/speaking/mad-roadshow-paris-2023/index.md
  66. 10
      content/french/speaking/matinale-it-for-business-devsecops-2022/index.md
  67. 6
      content/french/speaking/openshift-ai-roadshow-2024/index.md
  68. 12
      content/french/speaking/platform-day-2024/index.md
  69. 6
      content/french/speaking/red-hat-allie-strategie-edge/index.md
  70. 9
      content/french/speaking/red-hat-open-tour-lyon-2025/index.md
  71. 6
      content/french/speaking/red-hat-open-tour-toulouse-2024/index.md
  72. 3
      content/french/speaking/red-hat-openshift-user-group-3/index.md
  73. 3
      content/french/speaking/red-hat-openshift-user-group-4/index.md
  74. 7
      content/french/speaking/red-hat-openshift-user-group-5/index.md
  75. 10
      content/french/speaking/red-hat-summit-connect-france-2023/index.md
  76. 26
      content/french/speaking/red-hat-summit-connect-france-2024/index.md
  77. 2
      content/french/speaking/redhat-summit-connect-2021/index.md
  78. 10
      content/french/speaking/riviera-dev-2024/index.md
  79. 2
      content/french/speaking/worldline-tech-forum-explore-france-2024/index.md
  80. 1
      content/french/topics/_index.md
  81. 1
      content/french/writing/_index.md
  82. 24
      layouts/_markup/render-codeblock.html
  83. 88
      layouts/_markup/render-image.html
  84. 64
      layouts/_markup/render-link.html
  85. 2
      layouts/_partials/article-headline.html
  86. 64
      layouts/_partials/head.html
  87. 2
      layouts/_partials/icons/en.svg
  88. 37
      layouts/_partials/lang-detection.html
  89. 82
      layouts/_partials/opengraph.html
  90. 28
      layouts/_partials/page-description.html
  91. 2
      layouts/_partials/post-metadata.html
  92. 81
      layouts/_partials/social-image.html
  93. 1
      layouts/_partials/toolbar.html
  94. 36
      layouts/_partials/twitter_cards.html
  95. 11
      layouts/_shortcodes/attachedFigure.html
  96. 7
      layouts/_shortcodes/highlightFile.html
  97. 7
      layouts/_shortcodes/highlightWithTitle.html
  98. 31
      layouts/_shortcodes/internalLink.html
  99. 6
      layouts/_shortcodes/screenshotOf.html
  100. 2
      layouts/baseof.html

37
README.md

@ -65,6 +65,43 @@ Delete the old branch:
git branch -d "$(date +%F)-update" git branch -d "$(date +%F)-update"
``` ```
## Writing content
Standard Markdown is used, rendered by the render hooks in `layouts/_markup`.
Images (a standalone image is rendered as a figure, the title is the caption;
JPEG, PNG and WebP images are served as WebP in several widths):
```markdown
![Alternative text](image.png "Caption, *Markdown* allowed")
[![Tweet by @someone](tweet-someone.png)](https://twitter.com/someone/status/123)
```
Links to pages (with a flag when the target is in the other language; an empty
text is replaced by the title of the page) and to attached files:
```markdown
[](/blog/my-article.md)
[Some text](/blog/my-article.md#some-section)
[The slides](slides.pdf)
```
Code blocks, optionally with a file name or a title:
````markdown
```yaml {filename="config.yaml" hl_lines="3-5"}
```
```sh {title="CoreOS"}
```
````
Shortcodes: `youtube`, `embeddedVideo` (videos attached to the page) and
`attachedFileLink` (URL of an attached file, e.g. in a code block).
The old shortcodes (`attachedFigure`, `screenshotOf`, `internalLink`,
`highlightFile`, `highlightWithTitle`, `highlight`, `relref`) can be converted
with `scripts/convert-shortcodes.py FILE.md`.
## Theme ## Theme
The theme lives directly in this repository (`layouts`, `assets`, `i18n` and The theme lives directly in this repository (`layouts`, `assets`, `i18n` and

8
assets/css/itix.css

@ -41,9 +41,7 @@
*/ */
@font-face{ @font-face{
font-family: 'JetBrains Mono'; font-family: 'JetBrains Mono';
src: url('/fonts/JetBrainsMono-Regular.woff2') format('woff2'), src: url('/fonts/JetBrainsMono-Regular.woff2') format('woff2');
url('/fonts/JetBrainsMono-Regular.woff') format('woff'),
url('/fonts/JetBrainsMono-Regular.ttf') format('truetype');
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
@ -57,7 +55,7 @@
@font-face{ @font-face{
font-family: 'Source Serif Pro'; font-family: 'Source Serif Pro';
src: url('/fonts/SourceSerifPro-Regular.ttf') format('truetype'); src: url('/fonts/SourceSerifPro-Regular.woff2') format('woff2');
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
@ -71,7 +69,7 @@
@font-face{ @font-face{
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
src: url('/fonts/SourceSansPro-Regular.ttf') format('truetype'); src: url('/fonts/SourceSansPro-Regular.woff2') format('woff2');
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;

32
config.yaml

@ -6,6 +6,10 @@ enableRobotsTXT: true
capitalizeListTitles: false capitalizeListTitles: false
markup: markup:
goldmark:
parser:
# Standalone images are rendered as figures by the image render hook
wrapStandAloneImageWithinParagraph: false
highlight: highlight:
# Use CSS classes for syntax highlighting. The style sheet is generated with: # Use CSS classes for syntax highlighting. The style sheet is generated with:
# hugo gen chromastyles --style=borland > assets/css/chroma.css # hugo gen chromastyles --style=borland > assets/css/chroma.css
@ -16,6 +20,8 @@ languages:
en: en:
weight: 1 weight: 1
label: English label: English
params:
description: "Nicolas Massé's website: articles, talks and publications about Kubernetes, OpenShift, API Management, Single Sign-On and homelab."
menu: menu:
main: main:
- name: "Articles" - name: "Articles"
@ -42,6 +48,8 @@ languages:
fr: fr:
weight: 2 weight: 2
label: Français label: Français
params:
description: "Le site de Nicolas Massé : articles, conférences et publications sur Kubernetes, OpenShift, l'API Management, le Single Sign-On et le homelab."
menu: menu:
main: main:
- name: "Articles" - name: "Articles"
@ -70,6 +78,12 @@ languages:
# has not been translated (french files take precedence over english files). # has not been translated (french files take precedence over english files).
module: module:
mounts: mounts:
# Default assets mount, plus the images of the static directory so that
# they can be processed by Hugo (social images).
- source: assets
target: assets
- source: static/images
target: assets/images
- source: content/english - source: content/english
target: content target: content
sites: sites:
@ -86,6 +100,20 @@ module:
matrix: matrix:
languages: [fr] languages: [fr]
# Embed YouTube videos from youtube-nocookie.com: no cookie is set until
# the visitor plays the video.
privacy:
youtube:
privacyEnhanced: true
# The search index, used by the search page (static/js/search.js)
outputFormats:
searchindex:
mediaType: application/json
baseName: index
isPlainText: true
notAlternative: true
taxonomies: taxonomies:
opensource: "opensource" opensource: "opensource"
topic: "topics" topic: "topics"
@ -93,6 +121,10 @@ taxonomies:
params: params:
enablePlausible: false enablePlausible: false
# Used in the structured data (JSON-LD)
author:
name: "Nicolas Massé"
url: "https://www.linkedin.com/in/nicolasmasse/"
images: images:
- /images/avatar-2022-12.webp - /images/avatar-2022-12.webp

8
content/english/_index.md

@ -3,9 +3,9 @@ title: "Nicolas Massé"
--- ---
I am Nicolas Massé, you will find me on social networks under the nickname "nmasse-itix". I am Nicolas Massé, you will find me on social networks under the nickname "nmasse-itix".
Initially developer, I now work as pre-sales consultant in the fields of [API Management]({{<relref "/topics/api-management" >}}), [Single Sign On]({{<relref "/topics/openid-connect" >}}) and [Containers]({{<relref "/topics/containers" >}}). Initially developer, I now work as pre-sales consultant in the fields of [API Management](/topics/api-management), [Single Sign On](/topics/openid-connect) and [Containers](/topics/containers).
I mainly work with [Open Source]({{<relref "/opensource" >}}) technologies such as [Keycloak]({{<relref "/opensource/keycloak" >}}), [3scale]({{<relref "/opensource/3scale" >}}) or [OpenShift]({{<relref "/opensource/openshift" >}}). I mainly work with [Open Source](/opensource) technologies such as [Keycloak](/opensource/keycloak), [3scale](/opensource/3scale) or [OpenShift](/opensource/openshift).
I share my [expertise]({{<relref "/topics" >}}) and interests on this site, as a guest [on other websites]({{<relref "/writing" >}}) and I occasionally give [conferences]({{<relref "/speaking" >}}). I share my [expertise](/topics) and interests on this site, as a guest [on other websites](/writing) and I occasionally give [conferences](/speaking).
Please read [my blog]({{<relref "/blog" >}}) or [learn more about me](https://www.linkedin.com/in/nicolasmasse/). Please read [my blog](/blog) or [learn more about me](https://www.linkedin.com/in/nicolasmasse/).

4
content/english/blog/3scale-toolbox-anonymous-policy.md

@ -89,7 +89,7 @@ Import an OpenAPI Specification file that contains no security requirements.
Confirm your API has been imported as an *Open API*: it contains the *Anonymous* policy. Confirm your API has been imported as an *Open API*: it contains the *Anonymous* policy.
{{< highlight raw "hl_lines=4-11" >}} ```raw {hl_lines="4-11"}
$ curl -s "https://${TENANT}-admin.3scale.net/admin/api/nginx/spec.json?access_token=${TOKEN}"|jq '.services[]|select(.system_name == "toolbox_open_api")|.proxy.policy_chain' $ curl -s "https://${TENANT}-admin.3scale.net/admin/api/nginx/spec.json?access_token=${TOKEN}"|jq '.services[]|select(.system_name == "toolbox_open_api")|.proxy.policy_chain'
[ [
@ -107,7 +107,7 @@ $ curl -s "https://${TENANT}-admin.3scale.net/admin/api/nginx/spec.json?access_t
"configuration": {} "configuration": {}
} }
] ]
{{< / highlight >}} ```
Now, import an OpenAPI Specification file that contains a global security requirement **mandating API Key security**. Now, import an OpenAPI Specification file that contains a global security requirement **mandating API Key security**.

8
content/english/blog/3scale-toolbox-url-rewriting-policy.md

@ -21,14 +21,14 @@ By default, the public and private basepath have the same value and are taken fr
Let's examine a concrete example. Let's examine a concrete example.
You have an actual API implementation live at **/camel/my-route** and an API contract as follow: You have an actual API implementation live at **/camel/my-route** and an API contract as follow:
{{< highlight yaml "hl_lines=5" >}} ```yaml {hl_lines="5"}
swagger: '2.0' swagger: '2.0'
info: info:
title: 'Beer Catalog API' title: 'Beer Catalog API'
[...] [...]
basePath: /camel/my-route basePath: /camel/my-route
[...] [...]
{{< / highlight >}} ```
If you wish to expose it publicly *as-is* on **/camel/my-route**, there is nothing special to do, the toolbox will do the right thing. If you wish to expose it publicly *as-is* on **/camel/my-route**, there is nothing special to do, the toolbox will do the right thing.
@ -41,14 +41,14 @@ But if you want to expose it publicly on **/api/v1**, you will have to pass the
Let's examine another example. Let's examine another example.
You have an API contract stating that you want to expose your API publicly on **/api/v1** You have an API contract stating that you want to expose your API publicly on **/api/v1**
{{< highlight yaml "hl_lines=5" >}} ```yaml {hl_lines="5"}
swagger: '2.0' swagger: '2.0'
info: info:
title: 'Beer Catalog API' title: 'Beer Catalog API'
[...] [...]
basePath: /api/v1 basePath: /api/v1
[...] [...]
{{< / highlight >}} ```
If unfortunately your API backend is live at another *Base Path* (**/camel/my-route**), you will have to pass the following option to the *3scale import openapi* command: If unfortunately your API backend is live at another *Base Path* (**/camel/my-route**), you will have to pass the following option to the *3scale import openapi* command:

1
content/english/blog/_index.md

@ -1,3 +1,4 @@
--- ---
title: "Articles" title: "Articles"
description: "Technical articles about Kubernetes, OpenShift, API Management, Single Sign-On, containers and homelab, by Nicolas Massé."
--- ---

2
content/english/blog/airgap-openshift-installation-move-registry-created-using-oc-adm-release-mirror-between-environments/index.md

@ -147,7 +147,7 @@ Finally, skopeo is called to download each image to */tmp/oci_registry*.
with_items: '{{ images }}' with_items: '{{ images }}'
``` ```
The complete playbook [is available here]({{< attachedFileLink src="pull.yaml" >}}) and can be run as follow. The complete playbook [is available here](pull.yaml) and can be run as follow.
```sh ```sh
ansible-playbook pull.yaml -e token=$TOKEN ansible-playbook pull.yaml -e token=$TOKEN

28
content/english/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md

@ -16,7 +16,7 @@ resources:
- '*.gif' - '*.gif'
--- ---
At the {{< internalLink path="/speaking/red-hat-summit-connect-france-2024/index.md" >}}, I led a workshop for developers entitled "**Open Code Quest**". At the [](/speaking/red-hat-summit-connect-france-2024/index.md), I led a workshop for developers entitled "**Open Code Quest**".
In this workshop, developers had to code microservices using Quarkus, OpenShift and an Artificial Intelligence service: IBM's Granite model. In this workshop, developers had to code microservices using Quarkus, OpenShift and an Artificial Intelligence service: IBM's Granite model.
The workshop was designed as a speed competition: the first to complete all three exercises received a reward. The workshop was designed as a speed competition: the first to complete all three exercises received a reward.
@ -74,7 +74,7 @@ And that's precisely what we're interested in:
And if the three previous conditions are true, we can deduce that the user has completed and validated the **hero** exercise. And if the three previous conditions are true, we can deduce that the user has completed and validated the **hero** exercise.
Over time, these time series progress as shown in the figure below. Over time, these time series progress as shown in the figure below.
{{< attachedFigure src="exercise-validation.png" title="When the three conditions are met, the exercise is validated." >}} ![When the three conditions are met, the exercise is validated.](exercise-validation.png "When the three conditions are met, the exercise is validated.")
That's a good start, isn't it? That's a good start, isn't it?
If you do the same thing for all three exercises, you can see who has completed the whole workshop. If you do the same thing for all three exercises, you can see who has completed the whole workshop.
@ -83,7 +83,7 @@ Given that some exercises take longer than others, we could imagine awarding mor
This is the approach I've tried to model in the figure below, with a weighting of 55 for the first exercise, 30 for the second and 45 for the last. This is the approach I've tried to model in the figure below, with a weighting of 55 for the first exercise, 30 for the second and 45 for the last.
The idea is to approximate a linear progression of points over time (1 point per minute). The idea is to approximate a linear progression of points over time (1 point per minute).
{{< attachedFigure src="counting-scheme-no-time.png" title="Progression of the number of points for a normal, slow and fast user over time and with each exercise weighted according to the nominal duration of the exercise." >}} ![Progression of the number of points for a normal, slow and fast user over time and with each exercise weighted according to the nominal duration of the exercise.](counting-scheme-no-time.png "Progression of the number of points for a normal, slow and fast user over time and with each exercise weighted according to the nominal duration of the exercise.")
It's starting to come together. It's starting to come together.
But if you look closely, at the end of the workshop (at the 150th minute), all the participants have finished and have the same score. But if you look closely, at the end of the workshop (at the 150th minute), all the participants have finished and have the same score.
@ -107,12 +107,12 @@ Couldn't an accelerator be activated each time an exercise is validated, giving
That would make the competition more engaging and more fun! That would make the competition more engaging and more fun!
And that's what I've tried to model in the diagram below. And that's what I've tried to model in the diagram below.
{{< attachedFigure src="counting-scheme-with-time.png" title="Progression of the number of points for a normal, slow and fast user over time and with accelerator and weighting of each exercise according to the time it takes the user to complete the exercise." >}} ![Progression of the number of points for a normal, slow and fast user over time and with accelerator and weighting of each exercise according to the time it takes the user to complete the exercise.](counting-scheme-with-time.png "Progression of the number of points for a normal, slow and fast user over time and with accelerator and weighting of each exercise according to the time it takes the user to complete the exercise.")
Now the question is: does a user who takes the lead in the first exercise gain a significant advantage that would make the competition unbalanced? Now the question is: does a user who takes the lead in the first exercise gain a significant advantage that would make the competition unbalanced?
We found the answer during the various rehearsals that took place at Red Hat before D-Day. We found the answer during the various rehearsals that took place at Red Hat before D-Day.
{{< attachedFigure src="counting-scheme-dry-run.png" title="Validation of the point counting model during a dry-run." >}} ![Validation of the point counting model during a dry-run.](counting-scheme-dry-run.png "Validation of the point counting model during a dry-run.")
In the screenshot above, you can see that Batman completed the "hero" exercise **late**. In the screenshot above, you can see that Batman completed the "hero" exercise **late**.
But by completing the "villain" exercise **very quickly**, he was able to take back the lead... **temporarily**. But by completing the "villain" exercise **very quickly**, he was able to take back the lead... **temporarily**.
@ -168,7 +168,7 @@ Data should now appear in the Grafana dashboard.
To enjoy this to the full, stop the `run.sh` script by pressing **Ctrl + C** and run it again! To enjoy this to the full, stop the `run.sh` script by pressing **Ctrl + C** and run it again!
After a few seconds, you should see fresh data appear on the dashboard, as in the video below. After a few seconds, you should see fresh data appear on the dashboard, as in the video below.
{{< attachedFigure src="leaderboard-simulation.gif" title="Simulation of the Open Code Quest workshop on the testing workbench to validate the point-counting system (video accelerated 10x)." >}} ![Simulation of the Open Code Quest workshop on the testing workbench to validate the point-counting system (video accelerated 10x).](leaderboard-simulation.gif "Simulation of the Open Code Quest workshop on the testing workbench to validate the point-counting system (video accelerated 10x).")
### Prometheus queries ### Prometheus queries
@ -226,7 +226,7 @@ EOF
Copy and paste the query into the **Explore** section of Grafana and you should get the following graph. Copy and paste the query into the **Explore** section of Grafana and you should get the following graph.
{{< attachedFigure src="grafana-explore-opencodequest-leaderboard-hero.png" title="The metric \"opencodequest_leaderboard_hero:prod\" represents the completeness status of the exercise \"hero\" in the environment \"prod\"." >}} ![The metric "opencodequestleaderboardhero:prod" represents the completeness status of the exercise "hero" in the environment "prod".](grafana-explore-opencodequest-leaderboard-hero.png "The metric \"opencodequest_leaderboard_hero:prod\" represents the completeness status of the exercise \"hero\" in the environment \"prod\".")
It should be read as follows (note: 1728646377 = 13:32:57): It should be read as follows (note: 1728646377 = 13:32:57):
@ -289,7 +289,7 @@ It may seem complex at first, but in fact it's not that complex.
To understand how this query works, I suggest you split it into two parts: the `increase(...)` part on one side and the rest on the other. To understand how this query works, I suggest you split it into two parts: the `increase(...)` part on one side and the rest on the other.
We overlay this with the previous query and we get the following figure. We overlay this with the previous query and we get the following figure.
{{< attachedFigure src="grafana-opencodequest-leaderboard-onetime-bonus.png" title="The metric \"opencodequest_leaderboard_hero_onetime_bonus:prod\" represents the time bonus allocated to a user when they complete the \"hero\" exercise in the \"prod\" environment." >}} ![The metric "opencodequestleaderboardheroonetimebonus:prod" represents the time bonus allocated to a user when they complete the "hero" exercise in the "prod" environment.](grafana-opencodequest-leaderboard-onetime-bonus.png "The metric \"opencodequest_leaderboard_hero_onetime_bonus:prod\" represents the time bonus allocated to a user when they complete the \"hero\" exercise in the \"prod\" environment.")
From top to bottom, we can see: From top to bottom, we can see:
@ -319,7 +319,7 @@ This can be seen as the integral of the time serie.
The following figure shows how this query works in more detail. The following figure shows how this query works in more detail.
{{< attachedFigure src="grafana-opencodequest-leaderboard-lifetime-bonus.png" title="The metric \"opencodequest_leaderboard_hero_lifetime_bonus:prod\" represents the carry-over of the time bonus allocated to a user when he completes the exercise \"hero\" in the environment \"prod\"." >}} ![The metric "opencodequestleaderboardherolifetimebonus:prod" represents the carry-over of the time bonus allocated to a user when he completes the exercise "hero" in the environment "prod".](grafana-opencodequest-leaderboard-lifetime-bonus.png "The metric \"opencodequest_leaderboard_hero_lifetime_bonus:prod\" represents the carry-over of the time bonus allocated to a user when he completes the exercise \"hero\" in the environment \"prod\".")
From top to bottom, we can observe: From top to bottom, we can observe:
@ -364,7 +364,7 @@ In fact, the 3 accelerators already add up, so the user gains 1 point every 5 mi
The following figure shows the 6 Prometheus query components used to calculate the user's score. The following figure shows the 6 Prometheus query components used to calculate the user's score.
{{< attachedFigure src="grafana-opencodequest-leaderboard.png" title="The 6 components of the Prometheus query calculating user scores and the final result." >}} ![The 6 components of the Prometheus query calculating user scores and the final result.](grafana-opencodequest-leaderboard.png "The 6 components of the Prometheus query calculating user scores and the final result.")
### Recording Rules ### Recording Rules
@ -438,7 +438,7 @@ The `${user:regex}` syntax allows Grafana to replace `user=~"${user:regex}"` wit
To show the instant ranking, I used the **Bar Chart** visualisation with a **Sort by** transformation on the **Value** field. To show the instant ranking, I used the **Bar Chart** visualisation with a **Sort by** transformation on the **Value** field.
{{< attachedFigure src="grafana-opencodequest-leaderboard-instant-snapshot.png" title="Grafana visualisation settings for instant ranking." >}} ![Grafana visualisation settings for instant ranking.](grafana-opencodequest-leaderboard-instant-snapshot.png "Grafana visualisation settings for instant ranking.")
The important parameters of this visualisation are : The important parameters of this visualisation are :
@ -450,7 +450,7 @@ The important parameters of this visualisation are :
To track the progression of scores over time, I have opted for the **Time series** visualisation. To track the progression of scores over time, I have opted for the **Time series** visualisation.
{{< attachedFigure src="grafana-opencodequest-leaderboard-points-over-time.png" title="Grafana visualisation settings for score progression." >}} ![Grafana visualisation settings for score progression.](grafana-opencodequest-leaderboard-points-over-time.png "Grafana visualisation settings for score progression.")
The important parameters of this visualisation are : The important parameters of this visualisation are :
@ -476,7 +476,7 @@ As for the question on everyone's lips: did superheroes fight it out for the pod
The answer is a resounding **YES!** The answer is a resounding **YES!**
And there were plenty of thrills when the results were announced... And there were plenty of thrills when the results were announced...
{{< attachedFigure src="grafana-opencodequest-points.png" title="Progression of the Open Code Quest 74 participants' scores." >}} ![Progression of the Open Code Quest 74 participants' scores.](grafana-opencodequest-points.png "Progression of the Open Code Quest 74 participants' scores.")
Take a look at all those intersecting curves, all those superheroes competing for first place! Take a look at all those intersecting curves, all those superheroes competing for first place!
@ -488,4 +488,4 @@ The project not only highlighted technologies such as Quarkus, OpenShift and IBM
Designing the Leaderboard, although complex, added a motivating competitive dimension to the workshop. Designing the Leaderboard, although complex, added a motivating competitive dimension to the workshop.
On the day, watching the participants compete for speed while exploring Red Hat solutions was incredibly gratifying. On the day, watching the participants compete for speed while exploring Red Hat solutions was incredibly gratifying.
To find out how I implemented this Leaderboard in a multi-cluster architecture using Red Hat ACM, please visit: {{< internalLink path="/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md" >}}. To find out how I implemented this Leaderboard in a multi-cluster architecture using Red Hat ACM, please visit: [](/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md).

8
content/english/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md

@ -17,7 +17,7 @@ resources:
- '*.gif' - '*.gif'
--- ---
After revealing the behind-the-scenes design of the Leaderboard for the "Open Code Quest" workshop during the {{< internalLink path="/speaking/red-hat-summit-connect-france-2024/index.md" >}}, it's time to delve deeper into its practical implementation! After revealing the behind-the-scenes design of the Leaderboard for the "Open Code Quest" workshop during the [](/speaking/red-hat-summit-connect-france-2024/index.md), it's time to delve deeper into its practical implementation!
In this article, I'm going to take you through the configuration of **Red Hat Advanced Cluster Management** as well as the various adaptations needed to connect the *Leaderboard* created earlier with the **Open Code Quest** infrastructure. In this article, I'm going to take you through the configuration of **Red Hat Advanced Cluster Management** as well as the various adaptations needed to connect the *Leaderboard* created earlier with the **Open Code Quest** infrastructure.
@ -25,7 +25,7 @@ Come on board with me for this new stage, which is more technical than the previ
<!--more--> <!--more-->
This article follows on from {{< internalLink path="/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md" >}}. This article follows on from [](/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md).
If you haven't read it yet, I advise you to read it first to understand the context better. If you haven't read it yet, I advise you to read it first to understand the context better.
## Prometheus queries ## Prometheus queries
@ -227,7 +227,7 @@ Observability is an additional module (in the sense that it is not installed by
The following diagram shows the architecture of the observability module in **Red Hat Advanced Cluster Management**. The following diagram shows the architecture of the observability module in **Red Hat Advanced Cluster Management**.
I created it by observing the relationships between the components from an installation of ACM version 2.11. I created it by observing the relationships between the components from an installation of ACM version 2.11.
{{< attachedFigure src="redhat-acm-observability-architecture.svg" title="Logical architecture of observability in Red Hat Advanced Cluster Management 2.11" >}} ![Logical architecture of observability in Red Hat Advanced Cluster Management 2.11](redhat-acm-observability-architecture.svg "Logical architecture of observability in Red Hat Advanced Cluster Management 2.11")
The components deployed on the central cluster are in **green**, those deployed on the managed clusters are in **blue** and the configuration items are in **grey**. The components deployed on the central cluster are in **green**, those deployed on the managed clusters are in **blue** and the configuration items are in **grey**.
I've also illustrated the two possible places for calculating *recording rules*, in **yellow**. I've also illustrated the two possible places for calculating *recording rules*, in **yellow**.
@ -429,7 +429,7 @@ read -q "?press any key to continue "
./switch-to-grafana-admin.sh "$(oc whoami)" ./switch-to-grafana-admin.sh "$(oc whoami)"
``` ```
Then create the "Red Hat Summit Connect 2024" dashboard, as explained in the article {{< internalLink path="/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md" >}}. Then create the "Red Hat Summit Connect 2024" dashboard, as explained in the article [](/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md).
And finally, export the dashboard in the form of a ConfigMap. And finally, export the dashboard in the form of a ConfigMap.

28
content/english/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-and-qemu/index.md

@ -93,7 +93,7 @@ kubectl -n ci apply -f https://raw.githubusercontent.com/tektoncd/catalog/main/t
Create the **buildah** Task in the **ci** namespace as follow. Create the **buildah** Task in the **ci** namespace as follow.
{{< highlightFile "task.yaml" "yaml" "hl_lines=9-10 25-29 31 44-46 64-66" >}} ```yaml {filename="task.yaml" hl_lines="9-10 25-29 31 44-46 64-66"}
apiVersion: tekton.dev/v1beta1 apiVersion: tekton.dev/v1beta1
kind: Task kind: Task
metadata: metadata:
@ -164,7 +164,7 @@ spec:
push "$TARGET_IMAGE:latest" push "$TARGET_IMAGE:latest"
exit 0 exit 0
{{< /highlightFile >}} ```
The important parts of the task have been highlighted: The important parts of the task have been highlighted:
@ -175,7 +175,7 @@ The important parts of the task have been highlighted:
Create the **buildah-multiarch** Tekton Pipeline in the **ci** namespace as follow. Create the **buildah-multiarch** Tekton Pipeline in the **ci** namespace as follow.
The highlighted part of the pipeline contains the list of the target architectures. The highlighted part of the pipeline contains the list of the target architectures.
{{< highlightFile "pipeline.yaml" "yaml" "hl_lines=9-13" >}} ```yaml {filename="pipeline.yaml" hl_lines="9-13"}
apiVersion: tekton.dev/v1beta1 apiVersion: tekton.dev/v1beta1
kind: Pipeline kind: Pipeline
metadata: metadata:
@ -228,13 +228,13 @@ spec:
subPath: containers subPath: containers
taskRef: taskRef:
name: buildah name: buildah
{{< /highlightFile >}} ```
If the target container registry requires authentication to push a container image, you will need to create a **Service Account** and a **Secret**. If the target container registry requires authentication to push a container image, you will need to create a **Service Account** and a **Secret**.
Create the **tekton-robot** Secret in the **ci** namespace as follow. Create the **tekton-robot** Secret in the **ci** namespace as follow.
{{< highlightFile "serviceaccount.yaml" "yaml" "" >}} ```yaml {filename="serviceaccount.yaml"}
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
@ -243,11 +243,11 @@ secrets:
- name: quay-authentication - name: quay-authentication
imagePullSecrets: imagePullSecrets:
- name: quay-authentication - name: quay-authentication
{{< /highlightFile >}} ```
Create the secret to authenticate against your target registry (quay.io in my case) as follow. Create the secret to authenticate against your target registry (quay.io in my case) as follow.
{{< highlightFile "secret.yaml" "yaml" "" >}} ```yaml {filename="secret.yaml"}
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
@ -255,13 +255,13 @@ metadata:
data: data:
.dockerconfigjson: '[REDACTED]' .dockerconfigjson: '[REDACTED]'
type: kubernetes.io/dockerconfigjson type: kubernetes.io/dockerconfigjson
{{< /highlightFile >}} ```
Note: you can get this secret, by creating a [Robot Account](https://docs.quay.io/glossary/robot-accounts.html) under your [Organization](https://docs.quay.io/glossary/organizations.html). Note: you can get this secret, by creating a [Robot Account](https://docs.quay.io/glossary/robot-accounts.html) under your [Organization](https://docs.quay.io/glossary/organizations.html).
Then, you can assign it **write** permissions on the target repository. Then, you can assign it **write** permissions on the target repository.
Finally, you can click on your robot account and download the Kubernetes secret. Finally, you can click on your robot account and download the Kubernetes secret.
{{< attachedFigure src="quay-robot-account.png" title="Download the Kubernetes secret of your Quay robot account." >}} ![Download the Kubernetes secret of your Quay robot account.](quay-robot-account.png "Download the Kubernetes secret of your Quay robot account.")
At this stage, if you run the pipeline, it will fail and complains it cannot run ARMv8 binaries on a x84_64 host (**Exec format error**). At this stage, if you run the pipeline, it will fail and complains it cannot run ARMv8 binaries on a x84_64 host (**Exec format error**).
@ -269,7 +269,7 @@ At this stage, if you run the pipeline, it will fail and complains it cannot run
Deploy **Qemu** on all the nodes of your Kubernetes cluster by creating the **multiarch-qemu** DaemonSet in the namespace of you choice as follow. Deploy **Qemu** on all the nodes of your Kubernetes cluster by creating the **multiarch-qemu** DaemonSet in the namespace of you choice as follow.
{{< highlightFile "daemonset.yaml" "yaml" "" >}} ```yaml {filename="daemonset.yaml"}
apiVersion: apps/v1 apiVersion: apps/v1
kind: DaemonSet kind: DaemonSet
metadata: metadata:
@ -292,7 +292,7 @@ spec:
- /register --reset --persistent yes && while :; do sleep 3600; done - /register --reset --persistent yes && while :; do sleep 3600; done
securityContext: securityContext:
privileged: true privileged: true
{{< /highlightFile >}} ```
This DaemonSet will run a container that will configure Qemu, on each node of your Kubernetes cluster. This DaemonSet will run a container that will configure Qemu, on each node of your Kubernetes cluster.
@ -323,7 +323,7 @@ That's the magic behind it!
Create the **PipelineRun** in the **ci** namespace to start the pipeline. Create the **PipelineRun** in the **ci** namespace to start the pipeline.
Do not forget to change the **outputContainerImage** parameter to match the URL of your container registry! Do not forget to change the **outputContainerImage** parameter to match the URL of your container registry!
{{< highlightFile "pipelinerun.yaml" "yaml" "hl_lines=13" >}} ```yaml {filename="pipelinerun.yaml" hl_lines="13"}
apiVersion: tekton.dev/v1beta1 apiVersion: tekton.dev/v1beta1
kind: PipelineRun kind: PipelineRun
metadata: metadata:
@ -346,7 +346,7 @@ spec:
resources: resources:
requests: requests:
storage: 1Gi storage: 1Gi
{{< /highlightFile >}} ```
You can follow the pipeline execution with the **tkn** command. You can follow the pipeline execution with the **tkn** command.
@ -356,7 +356,7 @@ tkn -n ci pipelineruns logs -f
Once the pipeline finished, on my registry (Quay.io), I could see the container images for both architectures under the "latest" tag. Once the pipeline finished, on my registry (Quay.io), I could see the container images for both architectures under the "latest" tag.
{{< attachedFigure src="quay-repository.png" title="On Quay.io, you can see the container images for both architectures: ARMv8 and x86_64." >}} ![On Quay.io, you can see the container images for both architectures: ARMv8 and x8664.](quay-repository.png "On Quay.io, you can see the container images for both architectures: ARMv8 and x86_64.")
## Conclusion ## Conclusion

34
content/english/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-aws/index.md

@ -15,7 +15,7 @@ resources:
- '*.svg' - '*.svg'
--- ---
In 2022, I wrote an article on this subject named {{< internalLink path="/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-and-qemu/index.md" >}}. In 2022, I wrote an article on this subject named [](/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-and-qemu/index.md).
The article described the configuration I had set up for my personal projects. The article described the configuration I had set up for my personal projects.
It even went beyond its initial purpose as it has also been used by several colleagues at Red Hat who had the same need. It even went beyond its initial purpose as it has also been used by several colleagues at Red Hat who had the same need.
While the configuration described in this previous article is still relevant, the approach is somewhat dated. While the configuration described in this previous article is still relevant, the approach is somewhat dated.
@ -56,7 +56,7 @@ While the previous article targeted vanilla Kubernetes platforms, in this one I
In the Tekton pipelines, I use **Buildah** to build and push the container images to the registry. In the Tekton pipelines, I use **Buildah** to build and push the container images to the registry.
{{< attachedFigure src="overview.svg" title="Overview of the implemented configuration." >}} ![Overview of the implemented configuration.](overview.svg "Overview of the implemented configuration.")
## Deployment of OpenShift on AWS in Multi-Architecture Mode ## Deployment of OpenShift on AWS in Multi-Architecture Mode
@ -105,7 +105,7 @@ curl -sfL https://mirror.openshift.com/pub/openshift-v4/multi/clients/ocp/4.15.1
Create the **install-config.yaml** file. Create the **install-config.yaml** file.
The highlighted areas are the important points of the file. The highlighted areas are the important points of the file.
{{< highlightFile "install-config.yaml" "yaml" "hl_lines=10 15 26 29" >}} ```yaml {filename="install-config.yaml" hl_lines="10 15 26 29"}
additionalTrustBundlePolicy: Proxyonly additionalTrustBundlePolicy: Proxyonly
apiVersion: v1 apiVersion: v1
baseDomain: aws.itix.cloud baseDomain: aws.itix.cloud
@ -154,7 +154,7 @@ publish: External
pullSecret: 'REDACTED' pullSecret: 'REDACTED'
sshKey: | sshKey: |
REDACTED REDACTED
{{< /highlightFile >}} ```
I chose to keep the declaration of the Compute nodes but with replicas set to 0 because this simplifies the instantiation of the **x86_64** Compute node later on. I chose to keep the declaration of the Compute nodes but with replicas set to 0 because this simplifies the instantiation of the **x86_64** Compute node later on.
@ -169,7 +169,7 @@ As the [OpenShift 4.15 documentation](https://docs.openshift.com/container-platf
Edit the **manifests/cvo-overrides.yaml** file and add the **additionalEnabledCapabilities** section. Edit the **manifests/cvo-overrides.yaml** file and add the **additionalEnabledCapabilities** section.
Do not copy and paste the entire file below because the **clusterID** is specific to your installation! Do not copy and paste the entire file below because the **clusterID** is specific to your installation!
{{< highlightFile "manifests/cvo-overrides.yaml" "yaml" "hl_lines=10-21" >}} ```yaml {filename="manifests/cvo-overrides.yaml" hl_lines="10-21"}
apiVersion: config.openshift.io/v1 apiVersion: config.openshift.io/v1
kind: ClusterVersion kind: ClusterVersion
metadata: metadata:
@ -191,7 +191,7 @@ spec:
- OperatorLifecycleManager - OperatorLifecycleManager
- Build - Build
- DeploymentConfig - DeploymentConfig
{{< /highlightFile >}} ```
Start the cluster installation on AWS. Start the cluster installation on AWS.
@ -362,7 +362,7 @@ At this point, you should have three nodes in your cluster:
- 1 Compute node **x86_64** - 1 Compute node **x86_64**
- 1 Compute node **arm64** - 1 Compute node **arm64**
{{< attachedFigure src="openshift-console-three-nodes-x86_64-and-arm64.png" title="OpenShift console showing three nodes: two x86_64 nodes and one arm64." >}} ![OpenShift console showing three nodes: two x8664 nodes and one arm64.](openshift-console-three-nodes-x86_64-and-arm64.png "OpenShift console showing three nodes: two x86_64 nodes and one arm64.")
## Tekton operator installation ## Tekton operator installation
@ -453,7 +453,7 @@ spec:
Create an EFS volume by following the steps in the [AWS documentation](https://docs.aws.amazon.com/efs/latest/ug/gs-step-two-create-efs-resources.html). Create an EFS volume by following the steps in the [AWS documentation](https://docs.aws.amazon.com/efs/latest/ug/gs-step-two-create-efs-resources.html).
{{< attachedFigure src="aws-console-efs.png" title="AWS console showing an EFS volume." >}} ![AWS console showing an EFS volume.](aws-console-efs.png "AWS console showing an EFS volume.")
Create the **StorageClass** associated with the EFS volume. Create the **StorageClass** associated with the EFS volume.
The **fileSystemId** field matches to the second column in the screenshot above. The **fileSystemId** field matches to the second column in the screenshot above.
@ -476,7 +476,7 @@ parameters:
Following the documentation [OpenShift 4.15](https://docs.openshift.com/container-platform/4.15/storage/container_storage_interface/persistent-storage-csi-aws-efs.html#efs-create-volume_persistent-storage-csi-aws-efs), modify the EFS **Security Group** to allow OpenShift nodes to access storage via the NFS protocol. Following the documentation [OpenShift 4.15](https://docs.openshift.com/container-platform/4.15/storage/container_storage_interface/persistent-storage-csi-aws-efs.html#efs-create-volume_persistent-storage-csi-aws-efs), modify the EFS **Security Group** to allow OpenShift nodes to access storage via the NFS protocol.
{{< attachedFigure src="aws-console-security-group.png" title="AWS console showing the EFS Security Group configuration." >}} ![AWS console showing the EFS Security Group configuration.](aws-console-security-group.png "AWS console showing the EFS Security Group configuration.")
At this point, the AWS EFS service is accessible from the OpenShift cluster but is not yet usable by Tekton pipelines. At this point, the AWS EFS service is accessible from the OpenShift cluster but is not yet usable by Tekton pipelines.
Indeed, Tekton's **affinity-assistant** prohibits an RWX persistent volume from being mounted in two places at the same time (error *"more than one PersistentVolumeClaim is bound"*). Indeed, Tekton's **affinity-assistant** prohibits an RWX persistent volume from being mounted in two places at the same time (error *"more than one PersistentVolumeClaim is bound"*).
@ -533,7 +533,7 @@ The specifics of this task are related to the prerequisites necessary for runnin
- The `STORAGE_DRIVER=vfs` environment variable disables the use of **overlayfs**. - The `STORAGE_DRIVER=vfs` environment variable disables the use of **overlayfs**.
- Finally, the capability **SETFCAP** is assigned to **buildah**. - Finally, the capability **SETFCAP** is assigned to **buildah**.
{{< highlightFile "tekton/task-buildah-build.yaml" "yaml" "hl_lines=19-20 25-26 33-35 37-38" >}} ```yaml {filename="tekton/task-buildah-build.yaml" hl_lines="19-20 25-26 33-35 37-38"}
apiVersion: tekton.dev/v1beta1 apiVersion: tekton.dev/v1beta1
kind: Task kind: Task
metadata: metadata:
@ -572,7 +572,7 @@ spec:
volumeMounts: volumeMounts:
- name: container-storage - name: container-storage
mountPath: /var/lib/containers mountPath: /var/lib/containers
{{< /highlightFile >}} ```
The script executed by this task is concise (only two steps): The script executed by this task is concise (only two steps):
@ -638,11 +638,11 @@ The first use case presented at the beginning of this article is about the use o
The corresponding pipeline fetches the source code, executes a **buildah build** for each architecture (**x86_64** and **arm64**) and pushes the manifest to the registry. The corresponding pipeline fetches the source code, executes a **buildah build** for each architecture (**x86_64** and **arm64**) and pushes the manifest to the registry.
{{< attachedFigure src="tekton-pipeline-containerfile.png" title="OpenShift console showing running the multi-architecture Tekton pipeline (Containerfile building a container image of the `htop` utility)." >}} ![OpenShift console showing running the multi-architecture Tekton pipeline (Containerfile building a container image of the htop utility).](tekton-pipeline-containerfile.png "OpenShift console showing running the multi-architecture Tekton pipeline (Containerfile building a container image of the `htop` utility).")
The resulting image on the quay.io registry clearly shows a manifest containing two images: one for **x86_64** and one for **arm64**. The resulting image on the quay.io registry clearly shows a manifest containing two images: one for **x86_64** and one for **arm64**.
{{< attachedFigure src="quay-image-containerfile.png" title="Quay console showing a multi-architecture image of the `htop` utility)." >}} ![Quay console showing a multi-architecture image of the htop utility).](quay-image-containerfile.png "Quay console showing a multi-architecture image of the `htop` utility).")
### The "NodeJS" pipeline ### The "NodeJS" pipeline
@ -651,7 +651,7 @@ The NodeJS pipeline is significantly different in that the Git repository is fet
The reason is that the **npm install** command will download the application dependencies and some of these dependencies can embed native code (this is the case for some low-level libraries). The reason is that the **npm install** command will download the application dependencies and some of these dependencies can embed native code (this is the case for some low-level libraries).
Therefore, we need a separate *workspace* for each architecture. Therefore, we need a separate *workspace* for each architecture.
{{< attachedFigure src="tekton-pipeline-nodejs.png" title="OpenShift console showing running the multi-architecture Tekton pipeline building a NodeJS application." >}} ![OpenShift console showing running the multi-architecture Tekton pipeline building a NodeJS application.](tekton-pipeline-nodejs.png "OpenShift console showing running the multi-architecture Tekton pipeline building a NodeJS application.")
### The "Quarkus" Pipeline ### The "Quarkus" Pipeline
@ -659,7 +659,7 @@ The Quarkus pipeline is different from the NodeJS pipeline in the sense that the
The bytecode contained in Jar files can run on any architecture. The bytecode contained in Jar files can run on any architecture.
Therefore there is only one Git workspace shared between all architectures. Therefore there is only one Git workspace shared between all architectures.
{{< attachedFigure src="tekton-pipeline-quarkus.png" title="OpenShift console showing running the multi-architecture Tekton pipeline building a Quarkus application." >}} ![OpenShift console showing running the multi-architecture Tekton pipeline building a Quarkus application.](tekton-pipeline-quarkus.png "OpenShift console showing running the multi-architecture Tekton pipeline building a Quarkus application.")
### Link a task to an architecture ### Link a task to an architecture
@ -667,7 +667,7 @@ So far the article does not say how Tekton organizes itself to execute the task
There is no magic! It's all happening in the **PipelineRun**! There is no magic! It's all happening in the **PipelineRun**!
{{< highlightFile "examples/htop/tekton/pipelinerun.yaml" "yaml" "hl_lines=41-49" >}} ```yaml {filename="examples/htop/tekton/pipelinerun.yaml" hl_lines="41-49"}
apiVersion: tekton.dev/v1 apiVersion: tekton.dev/v1
kind: PipelineRun kind: PipelineRun
metadata: metadata:
@ -717,7 +717,7 @@ spec:
podTemplate: podTemplate:
nodeSelector: nodeSelector:
beta.kubernetes.io/arch: arm64 beta.kubernetes.io/arch: arm64
{{< /highlightFile >}} ```
The **taskRunSpecs** field allows you to add execution constraints to one or more pipeline tasks. The **taskRunSpecs** field allows you to add execution constraints to one or more pipeline tasks.

2
content/english/blog/configure-redhat-sso-3scale-cli/index.md

@ -98,7 +98,7 @@ You can use the following URL for the **OpenID Connect Issuer** (replace $SSO_HO
https://zync:s3cr3t@$SSO_HOSTNAME/auth/realms/3scale https://zync:s3cr3t@$SSO_HOSTNAME/auth/realms/3scale
``` ```
{{< attachedFigure src="openid-connect-issuer.png" title="OpenID Connect Issuer settings in the 3scale Admin Portal." >}} ![OpenID Connect Issuer settings in the 3scale Admin Portal.](openid-connect-issuer.png "OpenID Connect Issuer settings in the 3scale Admin Portal.")
Or when [deploying an API in 3scale with the 3scale toolbox](https://developers.redhat.com/blog/2019/07/29/3scale-toolbox-deploy-an-api-from-the-cli/), you can use: Or when [deploying an API in 3scale with the 3scale toolbox](https://developers.redhat.com/blog/2019/07/29/3scale-toolbox-deploy-an-api-from-the-cli/), you can use:

12
content/english/blog/consistent-dns-name-resolution-for-virtual-machines-and-containers/index.md

@ -48,7 +48,7 @@ Hence, if you try to query your container by its DNS name from your host, it wil
By configuring a third dnsmasq instance to gather the libvirt and podman ones, we achieve consistent DNS name resolution. By configuring a third dnsmasq instance to gather the libvirt and podman ones, we achieve consistent DNS name resolution.
The beauty of this setup is that you do not have to maintain this third dnsmasq instance since there is one provided with NetworkManager, and we will re-use it! The beauty of this setup is that you do not have to maintain this third dnsmasq instance since there is one provided with NetworkManager, and we will re-use it!
{{< attachedFigure src="dnsmasq-for-consistent-dns-resolution-vm-containers.svg" title="Three dnsmasq instances to get consistent DNS name resolution between VMs, containers and the host." >}} ![Three dnsmasq instances to get consistent DNS name resolution between VMs, containers and the host.](dnsmasq-for-consistent-dns-resolution-vm-containers.svg "Three dnsmasq instances to get consistent DNS name resolution between VMs, containers and the host.")
## Configure Podman ## Configure Podman
@ -113,7 +113,7 @@ This also prevents the infamous 5 seconds timeout on IPv6 DNS queries.
Create a file **/tmp/default-net.xml** that contains the following libvirt network definition. Create a file **/tmp/default-net.xml** that contains the following libvirt network definition.
{{< highlightFile "/tmp/default-net.xml" "xml" "" >}} ```xml {filename="/tmp/default-net.xml"}
<network xmlns:dnsmasq="http://libvirt.org/schemas/network/dnsmasq/1.0"> <network xmlns:dnsmasq="http://libvirt.org/schemas/network/dnsmasq/1.0">
<name>default</name> <name>default</name>
<forward mode='nat'> <forward mode='nat'>
@ -132,7 +132,7 @@ Create a file **/tmp/default-net.xml** that contains the following libvirt netwo
<dnsmasq:option value="auth-zone=dns.libvirt"/> <dnsmasq:option value="auth-zone=dns.libvirt"/>
</dnsmasq:options> </dnsmasq:options>
</network> </network>
{{< /highlightFile >}} ```
Replace the default libvirt network. Replace the default libvirt network.
@ -180,11 +180,11 @@ options edns0 trust-ad
Create the dnsmasq configuration file to dispatch requests to **dns.podman**, **dns.libvirt** and **dns.host**. Create the dnsmasq configuration file to dispatch requests to **dns.podman**, **dns.libvirt** and **dns.host**.
{{< highlightFile "/etc/NetworkManager/dnsmasq.d/podman-libvirt-dns.conf" "ini" "" >}} ```ini {filename="/etc/NetworkManager/dnsmasq.d/podman-libvirt-dns.conf"}
server=/dns.podman/10.88.0.1 server=/dns.podman/10.88.0.1
server=/dns.libvirt/192.168.122.1 server=/dns.libvirt/192.168.122.1
host-record=host.dns.host,192.168.122.1 host-record=host.dns.host,192.168.122.1
{{< /highlightFile >}} ```
Restart the dnsmasq instance of NetworkManager. Restart the dnsmasq instance of NetworkManager.
@ -237,6 +237,6 @@ dig +short www.google.fr
If your configuration is correct, you should get the following result. The top left terminal is the container, the bottom left terminal is the virtual machine and the right terminal is the host. If your configuration is correct, you should get the following result. The top left terminal is the container, the bottom left terminal is the virtual machine and the right terminal is the host.
{{< attachedFigure src="consistent-dns-resolution-in-vm-and-containers.png" title="Consistent DNS name resolution between VMs, containers and the host." >}} ![Consistent DNS name resolution between VMs, containers and the host.](consistent-dns-resolution-in-vm-and-containers.png "Consistent DNS name resolution between VMs, containers and the host.")
Congratulations! You just configured a consistent DNS name resolution between VMs, containers and the host. Congratulations! You just configured a consistent DNS name resolution between VMs, containers and the host.

8
content/english/blog/deploy-apis-from-your-cicd-pipeline-with-the-3scale-rest-api.md

@ -933,11 +933,11 @@ jq '.cors[0].configuration' policies.json > cors_schema.json
Create a sample configuration for the **cors** policy. Create a sample configuration for the **cors** policy.
{{< highlightFile "cors.json" "json" "" >}} ```json {filename="cors.json"}
{ {
"allow_credentials": true "allow_credentials": true
} }
{{< /highlightFile >}} ```
Validate that the configuration conforms to the extracted schema. Validate that the configuration conforms to the extracted schema.
@ -948,7 +948,7 @@ jsonschema -i cors.json cors_schema.json
Create the final policy chain. Create the final policy chain.
{{< highlightFile "policy_chain.json" "json" "" >}} ```json {filename="policy_chain.json"}
[ [
{ {
"name": "cors", "name": "cors",
@ -965,7 +965,7 @@ Create the final policy chain.
"enabled": true "enabled": true
} }
] ]
{{< /highlightFile >}} ```
You can then change the policy chain with the **Proxy Policies Chain Update** method. You can then change the policy chain with the **Proxy Policies Chain Update** method.
For instance, update the policy chain of the **echo** service with the new policy chain. For instance, update the policy chain of the **echo** service with the new policy chain.

14
content/english/blog/deploy-openshift-single-node-in-kvm/index.md

@ -21,7 +21,7 @@ This article explores how to deploy OpenShift Single Node (SNO) using KVM on you
On your Fedora workstation, make sure you installed the required packages [as explained in the Fedora documentation](https://docs.fedoraproject.org/en-US/quick-docs/getting-started-with-virtualization/). On your Fedora workstation, make sure you installed the required packages [as explained in the Fedora documentation](https://docs.fedoraproject.org/en-US/quick-docs/getting-started-with-virtualization/).
Configure {{<internalLink path="/blog/consistent-dns-name-resolution-for-virtual-machines-and-containers/index.md" title="NetworkManager to use dnsmasq" >}} for DNS resolution. Configure [NetworkManager to use dnsmasq](/blog/consistent-dns-name-resolution-for-virtual-machines-and-containers/index.md) for DNS resolution.
Choose a base domain name and a cluster name. Choose a base domain name and a cluster name.
The base domain does not need to be public or registered somewhere. The base domain does not need to be public or registered somewhere.
@ -44,7 +44,7 @@ You can get one for free by registering as a developer [here](https://developers
Create the libvirt network definition. Create the libvirt network definition.
{{< highlightFile "ocp-net.xml" "xml" "" >}} ```xml {filename="ocp-net.xml"}
<network xmlns:dnsmasq="http://libvirt.org/schemas/network/dnsmasq/1.0"> <network xmlns:dnsmasq="http://libvirt.org/schemas/network/dnsmasq/1.0">
<name>ocp-dev</name> <name>ocp-dev</name>
<forward mode='nat'> <forward mode='nat'>
@ -72,7 +72,7 @@ Create the libvirt network definition.
<dnsmasq:option value="cname=*.apps.itix-dev.ocp.itix,lb.itix-dev.ocp.itix"/> <dnsmasq:option value="cname=*.apps.itix-dev.ocp.itix,lb.itix-dev.ocp.itix"/>
</dnsmasq:options> </dnsmasq:options>
</network> </network>
{{< /highlightFile >}} ```
This file defines a libvirt network named **ocp-dev** and add the required DNS records for a standard installation of OpenShift. This file defines a libvirt network named **ocp-dev** and add the required DNS records for a standard installation of OpenShift.
Make sure to replace **itix-dev.ocp.itix** by your cluster name and base domain. Make sure to replace **itix-dev.ocp.itix** by your cluster name and base domain.
@ -106,7 +106,7 @@ Configure the cluster.
* Check **I want to install single node OpenShift (SNO)** * Check **I want to install single node OpenShift (SNO)**
* Click **Next** * Click **Next**
{{< attachedFigure src="assisted-installer-1.png" title="Assisted Installer: configure the cluster." >}} ![Assisted Installer: configure the cluster.](assisted-installer-1.png "Assisted Installer: configure the cluster.")
Generate the ISO image. Generate the ISO image.
@ -131,7 +131,7 @@ On cloud.redhat.com, wait for the node to appear in the list.
* Click **Next**. * Click **Next**.
{{< attachedFigure src="assisted-installer-2.png" title="Assisted Installer: wait for the node to appear in the list." >}} ![Assisted Installer: wait for the node to appear in the list.](assisted-installer-2.png "Assisted Installer: wait for the node to appear in the list.")
* Select your Network Subnet in the dropdown list. * Select your Network Subnet in the dropdown list.
* Click **Next**. * Click **Next**.
@ -140,10 +140,10 @@ On cloud.redhat.com, wait for the node to appear in the list.
You can follow the installation process from cloud.redhat.com. You can follow the installation process from cloud.redhat.com.
Once the installation is complete, download the **kubeadmin** password and make sure you can access the OpenShift Console. Once the installation is complete, download the **kubeadmin** password and make sure you can access the OpenShift Console.
{{< attachedFigure src="assisted-installer-5.png" title="Assisted Installer: the installation is complete." >}} ![Assisted Installer: the installation is complete.](assisted-installer-5.png "Assisted Installer: the installation is complete.")
Also, make sure you can login with SSH to your OpenShift node. Also, make sure you can login with SSH to your OpenShift node.
If you configured NetworkManager and dnsmasq {{<internalLink path="/blog/consistent-dns-name-resolution-for-virtual-machines-and-containers/index.md" title="as explained here" >}}, you should be able to reach your OpenShift node by its DNS name. If you configured NetworkManager and dnsmasq [as explained here](/blog/consistent-dns-name-resolution-for-virtual-machines-and-containers/index.md), you should be able to reach your OpenShift node by its DNS name.
Otherwise, use the IP address. Otherwise, use the IP address.
```sh ```sh

12
content/english/blog/enable-global-policies-apicast.md

@ -24,7 +24,7 @@ Start from those default *Environment Files* and add a `policy_chain` field with
The default *Global Policy Chain* can be found in the [`gateway/src/apicast/policy_chain.lua`](https://github.com/3scale/APIcast/blob/b8f7f067dd47936f93bc9bd3e6de224c304d58ea/gateway/src/apicast/policy_chain.lua#L67-L72) file. The default *Global Policy Chain* can be found in the [`gateway/src/apicast/policy_chain.lua`](https://github.com/3scale/APIcast/blob/b8f7f067dd47936f93bc9bd3e6de224c304d58ea/gateway/src/apicast/policy_chain.lua#L67-L72) file.
**production.lua:** **production.lua:**
{{< highlight lua "hl_lines=8-14" >}} ```lua {hl_lines="8-14"}
return { return {
master_process = 'on', master_process = 'on',
lua_code_cache = 'on', lua_code_cache = 'on',
@ -40,10 +40,10 @@ return {
'apicast.policy.nginx_metrics' 'apicast.policy.nginx_metrics'
}), }),
} }
{{< / highlight >}} ```
**staging.lua:** **staging.lua:**
{{< highlight lua "hl_lines=7-13" >}} ```lua {hl_lines="7-13"}
return { return {
master_process = 'on', master_process = 'on',
lua_code_cache = 'on', lua_code_cache = 'on',
@ -58,12 +58,12 @@ return {
'apicast.policy.nginx_metrics' 'apicast.policy.nginx_metrics'
}), }),
} }
{{< / highlight >}} ```
Then, create a ConfigMap from those two files and mount it in `/opt/app-root/src/config`: Then, create a ConfigMap from those two files and mount it in `/opt/app-root/src/config`:
{{< highlight sh >}} ```sh
oc create configmap apicast-cors --from-file=production.lua --from-file=staging.lua oc create configmap apicast-cors --from-file=production.lua --from-file=staging.lua
oc set volume dc/apicast-production --add --name=apicast-cors -t configmap --configmap-name=apicast-cors -m /opt/app-root/src/config oc set volume dc/apicast-production --add --name=apicast-cors -t configmap --configmap-name=apicast-cors -m /opt/app-root/src/config
oc set volume dc/apicast-staging --add --name=apicast-cors -t configmap --configmap-name=apicast-cors -m /opt/app-root/src/config oc set volume dc/apicast-staging --add --name=apicast-cors -t configmap --configmap-name=apicast-cors -m /opt/app-root/src/config
{{< / highlight >}} ```

28
content/english/blog/homelab-server-2u-short-depth-front-io-ampere-altra-arm64-architecture/index.md

@ -33,9 +33,9 @@ This **HP DL20 Gen9** server is equipped with :
- 2 x 3.5" disks, 4 TB each, configured as RAID 1 using the HP RAID card - 2 x 3.5" disks, 4 TB each, configured as RAID 1 using the HP RAID card
- 6 x 1 GbE RJ-45 ports - 6 x 1 GbE RJ-45 ports
{{< attachedFigure src="hp-dl20-gen9.webp" title="The existing HP DL20 Gen9 server." >}} ![The existing HP DL20 Gen9 server.](hp-dl20-gen9.webp "The existing HP DL20 Gen9 server.")
This server is currently racked in a 7U, short depth IT rack in the basement of the house and is currently running **CentOS Stream 8** which I have configured as a router (see {{< internalLink path="/blog/fibre-orange-remplacer-livebox-routeur-centos-stream-8" >}}) and hypervisor (with **libvirt**). This server is currently racked in a 7U, short depth IT rack in the basement of the house and is currently running **CentOS Stream 8** which I have configured as a router (see [](/blog/fibre-orange-remplacer-livebox-routeur-centos-stream-8)) and hypervisor (with **libvirt**).
The hypervisor runs several VMs: The hypervisor runs several VMs:
@ -68,7 +68,7 @@ The expansion capabilities of the **HP DL20 Gen9** are quite limited: 2 PCIe x8
The full-height slot is taken by the RAID controller and the half-height slot is taken by the 4-port GbE network card. The full-height slot is taken by the RAID controller and the half-height slot is taken by the 4-port GbE network card.
Last but not least, the motherboard and CPU of the **HP DL20 Gen9** do not support **SR-IOV**. This currently forces me to run network routing functions directly on the hypervisor. Last but not least, the motherboard and CPU of the **HP DL20 Gen9** do not support **SR-IOV**. This currently forces me to run network routing functions directly on the hypervisor.
SR-IOV would allow me to put the {{<internalLink path="/blog/fibre-orange-remplacer-livebox-routeur-centos-stream-8" title="CentOS Stream router" >}} into a VM without performance loss. SR-IOV would allow me to put the [CentOS Stream router](/blog/fibre-orange-remplacer-livebox-routeur-centos-stream-8) into a VM without performance loss.
To sum up, I need : To sum up, I need :
@ -143,7 +143,7 @@ Then it will still be time to upgrade to a more powerful CPU.
The **Ampere Altra Q64-22** CPU has a TDP of 69 W according to the [Ampere Altra datasheet](https://d1o0i0v5q5lp8h.cloudfront.net/ampere/live/assets/documents/Altra_Rev_A1_DS_v1.27_20220331.pdf). The **Ampere Altra Q64-22** CPU has a TDP of 69 W according to the [Ampere Altra datasheet](https://d1o0i0v5q5lp8h.cloudfront.net/ampere/live/assets/documents/Altra_Rev_A1_DS_v1.27_20220331.pdf).
{{< attachedFigure src="ampere-altra-q64-22.jpeg" title="The Ampere Altra Q64-22 CPU, mounted on the ASRock Rack ALTRAD8UD-1L2T motherboard" >}} ![The Ampere Altra Q64-22 CPU, mounted on the ASRock Rack ALTRAD8UD-1L2T motherboard](ampere-altra-q64-22.jpeg "The Ampere Altra Q64-22 CPU, mounted on the ASRock Rack ALTRAD8UD-1L2T motherboard")
Another difficulty is that I live in France, and Newegg doesn't ship to France... Another difficulty is that I live in France, and Newegg doesn't ship to France...
So I had to go through a reshipper: a company located the USA that receives packages and ships them all over the world. So I had to go through a reshipper: a company located the USA that receives packages and ships them all over the world.
@ -187,7 +187,7 @@ Before deciding to buy the [Innovision M24306](http://iovserver.com/2u-server-ca
The **Innovision M24306** is available on [Aliexpress](https://www.aliexpress.us/item/1005005856237609.html) and [Alibaba](https://www.alibaba.com/product-detail/Ultra-Short-2U-rackmount-Server-Chassis_1600636420062.html). The **Innovision M24306** is available on [Aliexpress](https://www.aliexpress.us/item/1005005856237609.html) and [Alibaba](https://www.alibaba.com/product-detail/Ultra-Short-2U-rackmount-Server-Chassis_1600636420062.html).
{{< attachedFigure src="innovision-m24306.jpeg" title="The Innovision M24306 case." >}} ![The Innovision M24306 case.](innovision-m24306.jpeg "The Innovision M24306 case.")
Some useful notes (I asked for clarification before ordering): Some useful notes (I asked for clarification before ordering):
@ -200,7 +200,7 @@ Some useful notes (I asked for clarification before ordering):
Note: the VGA connector on the server's right ear is not connected to anything. Note: the VGA connector on the server's right ear is not connected to anything.
But this isn't a problem in the sense that the case has its inputs/outputs on the front side, so the VGA connector is already on the front side and doesn't need to be remoted. But this isn't a problem in the sense that the case has its inputs/outputs on the front side, so the VGA connector is already on the front side and doesn't need to be remoted.
{{< attachedFigure src="vga-not-connected.jpeg" title="The VGA socket on the right ear of the Innovision M24306 is not connected." >}} ![The VGA socket on the right ear of the Innovision M24306 is not connected.](vga-not-connected.jpeg "The VGA socket on the right ear of the Innovision M24306 is not connected.")
## FSP CRPS power supply ## FSP CRPS power supply
@ -228,7 +228,7 @@ The [FSP-FC250](https://www.fsp-group.com/download/pro/FSP-FC250_Datasheet.pdf),
I then opted for the lowest-powered power supply module available, as 2 x 550W is already a lot for a small server! I then opted for the lowest-powered power supply module available, as 2 x 550W is already a lot for a small server!
This is the [FSP550-20FM](https://www.fsp-group.com/download/pro/FSP550-20FM_Datasheet.pdf) model. This is the [FSP550-20FM](https://www.fsp-group.com/download/pro/FSP550-20FM_Datasheet.pdf) model.
{{< attachedFigure src="psu-fsp.webp" title="The composition of the FSP CRPS power supply." >}} ![The composition of the FSP CRPS power supply.](psu-fsp.webp "The composition of the FSP CRPS power supply.")
Please note that the Power Distribution Board is only fixed to the case by two small screws. Please note that the Power Distribution Board is only fixed to the case by two small screws.
To prevent it from moving, I had to shim it with a piece of foam. To prevent it from moving, I had to shim it with a piece of foam.
@ -257,7 +257,7 @@ The reference **CAB-8654/8654-8i-11-P0.5M-85** from [10Gtek](https://www.10gtek.
- Finally, you can opt for angled or straight connectors. - Finally, you can opt for angled or straight connectors.
I chose the **straight connectors**. I chose the **straight connectors**.
{{< attachedFigure src="slimsas-cables.webp" title="SlimSAS / SFF-8654 cable routing in the Innovision M24306 box." >}} ![SlimSAS / SFF-8654 cable routing in the Innovision M24306 box.](slimsas-cables.webp "SlimSAS / SFF-8654 cable routing in the Innovision M24306 box.")
## Storage ## Storage
@ -271,7 +271,7 @@ These SSDs are in *Add-in Card (AIC)*, half-height, half-length (**HHHL**) forma
But for the time being, it keeps me going. But for the time being, it keeps me going.
And if I find an eBay auction with a batch of Samsung PM1733 SSDs at a good price, I'll just have to transfer the data to the new SSDs to get 3 PCIe slots back. And if I find an eBay auction with a batch of Samsung PM1733 SSDs at a good price, I'll just have to transfer the data to the new SSDs to get 3 PCIe slots back.
{{< attachedFigure src="nvme-storage.jpeg" title="Three Samsung PM1735 SSDs and one Samsung 980 PRO SSD installed in the case." >}} ![Three Samsung PM1735 SSDs and one Samsung 980 PRO SSD installed in the case.](nvme-storage.jpeg "Three Samsung PM1735 SSDs and one Samsung 980 PRO SSD installed in the case.")
It should be noted that the **Samsung PM1733A** SSDs have been tested by **Ampere Computing** and have shown good performance with their CPU! See [Samsung FIO Performance - Solution Brief](https://amperecomputing.com/briefs/samsung-FIO-performance). It should be noted that the **Samsung PM1733A** SSDs have been tested by **Ampere Computing** and have shown good performance with their CPU! See [Samsung FIO Performance - Solution Brief](https://amperecomputing.com/briefs/samsung-FIO-performance).
@ -279,7 +279,7 @@ On top of that, I've added an NVMe SSD in M.2 format to host the operating syste
I also tested six U.2 format (PCIe 3.0) NVMe SSDs on the case's NVMe backplane. And it works! However, I didn't test the hotplug feature, as I don't think the case's backplane has it implemented. I also tested six U.2 format (PCIe 3.0) NVMe SSDs on the case's NVMe backplane. And it works! However, I didn't test the hotplug feature, as I don't think the case's backplane has it implemented.
{{< attachedFigure src="more-nvme-storage.webp" title="Six more NVMe SSDs, for a total of 26.4 TB of NVMe storage!" >}} ![Six more NVMe SSDs, for a total of 26.4 TB of NVMe storage!](more-nvme-storage.webp "Six more NVMe SSDs, for a total of 26.4 TB of NVMe storage!")
## Bill of Materials ## Bill of Materials
@ -307,7 +307,7 @@ And in this price, I haven't counted shipping costs, customs duties, etc. 💸
It should be noted that, by default, the airflow of the **FSP 550-20FM** power supply and **Innovision M24306** case fans is reversed: the power supply fans blow in a **front to back** direction, while the case fans blow in a **back to front** direction. It should be noted that, by default, the airflow of the **FSP 550-20FM** power supply and **Innovision M24306** case fans is reversed: the power supply fans blow in a **front to back** direction, while the case fans blow in a **back to front** direction.
Fortunately, the case fans can be reversed. Fortunately, the case fans can be reversed.
{{< attachedFigure src="fans-air-flow.webp" title="I flipped all four fans to have front to back airflow. In this configuration, the fan labels are not visible from inside the case." >}} ![I flipped all four fans to have front to back airflow. In this configuration, the fan labels are not visible from inside the case.](fans-air-flow.webp "I flipped all four fans to have front to back airflow. In this configuration, the fan labels are not visible from inside the case.")
I haven't done a performance test yet, but the temperatures measured are lower with this optimized airflow. I haven't done a performance test yet, but the temperatures measured are lower with this optimized airflow.
The effect is most noticeable in the 6 NVMe SSDs on the left. The effect is most noticeable in the 6 NVMe SSDs on the left.
@ -320,15 +320,15 @@ The three unused slots can be masked to force airflow to where it's most needed.
The external appearance of the product is rather flattering, almost professional. The external appearance of the product is rather flattering, almost professional.
{{< attachedFigure src="final-product.webp" title="2U server based on an Innovision M24306 case, an ASRock Rack ALTRAD8UD-1L2T motherboard and an Ampere Altra Q64-22 CPU. Front view is on the top, rear view is on the bottom." >}} ![2U server based on an Innovision M24306 case, an ASRock Rack ALTRAD8UD-1L2T motherboard and an Ampere Altra Q64-22 CPU. Front view is on the top, rear view is on the bottom.](final-product.webp "2U server based on an Innovision M24306 case, an ASRock Rack ALTRAD8UD-1L2T motherboard and an Ampere Altra Q64-22 CPU. Front view is on the top, rear view is on the bottom.")
The interior isn't as well designed as an HP or Dell server, but I'd be quite happy with it. 😎 The interior isn't as well designed as an HP or Dell server, but I'd be quite happy with it. 😎
{{< attachedFigure src="internal-layout.webp" title="Internal layout of the Innovision M24306 case with an ASRock Rack ALTRAD8UD-1L2T motherboard and an Ampere Altra Q64-22 CPU." >}} ![Internal layout of the Innovision M24306 case with an ASRock Rack ALTRAD8UD-1L2T motherboard and an Ampere Altra Q64-22 CPU.](internal-layout.webp "Internal layout of the Innovision M24306 case with an ASRock Rack ALTRAD8UD-1L2T motherboard and an Ampere Altra Q64-22 CPU.")
Last photos, this time with the cover in place, ready to be racked! 🚀 Last photos, this time with the cover in place, ready to be racked! 🚀
{{< attachedFigure src="perspective-view.webp" title="Innovision M24306 case with an ASRock Rack ALTRAD8UD-1L2T motherboard and an Ampere Altra Q64-22 CPU." >}} ![Innovision M24306 case with an ASRock Rack ALTRAD8UD-1L2T motherboard and an Ampere Altra Q64-22 CPU.](perspective-view.webp "Innovision M24306 case with an ASRock Rack ALTRAD8UD-1L2T motherboard and an Ampere Altra Q64-22 CPU.")
## Conclusion ## Conclusion

22
content/english/blog/how-to-run-performance-tests-with-k6-prometheus-grafana/index.md

@ -27,7 +27,7 @@ This lack has been identified by the community and is tracked through the [GitHu
In the meantime, we can use StatsD as a bridge between K6 and Prometheus since K6 has a native support for StatsD and Prometheus handle the StatsD protocol through its [statsd_exporter](https://github.com/prometheus/statsd_exporter). In the meantime, we can use StatsD as a bridge between K6 and Prometheus since K6 has a native support for StatsD and Prometheus handle the StatsD protocol through its [statsd_exporter](https://github.com/prometheus/statsd_exporter).
{{< attachedFigure src="k6-statsd-prometheus.svg" title="K6 pushes its metrics to the statsd_exporter while Prometheus scrapes the statsd_exporter." >}} ![K6 pushes its metrics to the statsdexporter while Prometheus scrapes the statsdexporter.](k6-statsd-prometheus.svg "K6 pushes its metrics to the statsd_exporter while Prometheus scrapes the statsd_exporter.")
K6 has two implementations of the StatsD protocol: statsd and datadog. K6 has two implementations of the StatsD protocol: statsd and datadog.
The datadog implementation has the advantage of enabling the tags extension in the StatsD protocol. The datadog implementation has the advantage of enabling the tags extension in the StatsD protocol.
@ -40,7 +40,7 @@ This article explains how to set it up on Fedora using Podman.
Create the statsd_exporter configuration file. Create the statsd_exporter configuration file.
{{< highlightFile "statsd_exporter.yaml" "yaml" "" >}} ```yaml {filename="statsd_exporter.yaml"}
defaults: defaults:
observer_type: histogram observer_type: histogram
mappings: mappings:
@ -52,7 +52,7 @@ mappings:
http_name: "$1" http_name: "$1"
check_name: "$2" check_name: "$2"
outcome: "$3" outcome: "$3"
{{< /highlightFile >}} ```
This configuration instructs statsd_exporter to: This configuration instructs statsd_exporter to:
@ -69,7 +69,7 @@ Create the Prometheus configuration that scrapes the statsd_exporter every secon
It also filters out some labels that are not used in the Grafana dashboard. It also filters out some labels that are not used in the Grafana dashboard.
It is important to filter out the **url** label since it can lead to [high cardinalities](https://www.robustperception.io/cardinality-is-key) in Prometheus. It is important to filter out the **url** label since it can lead to [high cardinalities](https://www.robustperception.io/cardinality-is-key) in Prometheus.
{{< highlightFile "prometheus.yaml" "yaml" "" >}} ```yaml {filename="prometheus.yaml"}
global: global:
scrape_interval: 1s scrape_interval: 1s
evaluation_interval: 1s evaluation_interval: 1s
@ -82,7 +82,7 @@ scrape_configs:
metric_relabel_configs: metric_relabel_configs:
- regex: '(job|instance|url)' - regex: '(job|instance|url)'
action: labeldrop action: labeldrop
{{< /highlightFile >}} ```
Start the Prometheus server. Start the Prometheus server.
@ -108,7 +108,7 @@ CONTAINER ID IMAGE COMMAND C
Open [http://localhost:9090/targets](http://localhost:9090/targets) and make sure Prometheus can scrape the statsd_exporter. Open [http://localhost:9090/targets](http://localhost:9090/targets) and make sure Prometheus can scrape the statsd_exporter.
{{< attachedFigure src="prometheus-targets.png" title="Prometheus successfully scrapes the statsd_exporter." >}} ![Prometheus successfully scrapes the statsdexporter.](prometheus-targets.png "Prometheus successfully scrapes the statsd_exporter.")
Open [http://localhost:3000/](http://localhost:3000/) and login with **admin** / **admin**. Open [http://localhost:3000/](http://localhost:3000/) and login with **admin** / **admin**.
@ -150,7 +150,7 @@ sudo dnf install k6
There was a couple Grafana dashboards available in the community but none of them matched my requirements. There was a couple Grafana dashboards available in the community but none of them matched my requirements.
So I designed my own that features measurement of the seven standard Go HTTP timings and tracking of multiple K6 HTTP request (by their name). So I designed my own that features measurement of the seven standard Go HTTP timings and tracking of multiple K6 HTTP request (by their name).
{{< attachedFigure src="k6-grafana-dashboard.png" title="Screenshot of the Grafana dashboard while K6 is conducting a performance test." >}} ![Screenshot of the Grafana dashboard while K6 is conducting a performance test.](k6-grafana-dashboard.png "Screenshot of the Grafana dashboard while K6 is conducting a performance test.")
The dashboard is divided in three parts: The dashboard is divided in three parts:
@ -183,7 +183,7 @@ You can try the whole stack with this simple K6 test.
It starts by defining the number of VUs over three stages: ramp-up, steady, ramp-down. It starts by defining the number of VUs over three stages: ramp-up, steady, ramp-down.
It adds error counting (how many JavaScript exceptions during a test run), a very simple test function and wraps this test function with error counting. It adds error counting (how many JavaScript exceptions during a test run), a very simple test function and wraps this test function with error counting.
{{< highlightFile "simple-test.js" "js" "" >}} ```js {filename="simple-test.js"}
import http from "k6/http"; import http from "k6/http";
import { check, sleep } from "k6"; import { check, sleep } from "k6";
import { Rate } from "k6/metrics"; import { Rate } from "k6/metrics";
@ -223,7 +223,7 @@ function simpleTest() {
} }
export default wrapWithErrorCounting(simpleTest); export default wrapWithErrorCounting(simpleTest);
{{< /highlightFile >}} ```
You can run the test with the **k6 run** command. You can run the test with the **k6 run** command.
@ -233,13 +233,13 @@ k6 run -o datadog simple-test.js
If everything went fine, you should see the k6 summary in the console. If everything went fine, you should see the k6 summary in the console.
{{< attachedFigure src="k6-simple-test2.png" title="K6 prints a summary upon completion." >}} ![K6 prints a summary upon completion.](k6-simple-test2.png "K6 prints a summary upon completion.")
In the grafana dashboard, select **simple-test** in the **K6 request name** dropdown list and **req_duration** in the **Go HTTP Metric Name** dropdown list. In the grafana dashboard, select **simple-test** in the **K6 request name** dropdown list and **req_duration** in the **Go HTTP Metric Name** dropdown list.
Note: you might have to hit **CTRL-R** to reload the page if the dropdown list is empty. Note: you might have to hit **CTRL-R** to reload the page if the dropdown list is empty.
Then, the grafana dashboard should look like this. Then, the grafana dashboard should look like this.
{{< attachedFigure src="k6-simple-test.png" title="The grafana dashboard shows a K6 test run." >}} ![The grafana dashboard shows a K6 test run.](k6-simple-test.png "The grafana dashboard shows a K6 test run.")
## Conclusion ## Conclusion

2
content/english/blog/install-openwrt-raspberry-pi/index.md

@ -72,7 +72,7 @@ screen /dev/ttyUSB0 115200
You then need to connect the VIN, GND, TXD and RXD wires to the correct GPIO pins of your Raspberry PI. You then need to connect the VIN, GND, TXD and RXD wires to the correct GPIO pins of your Raspberry PI.
{{< attachedFigure src="uart-pins.jpeg" title="The UART Pins of the Raspberry PI 3" >}} ![The UART Pins of the Raspberry PI 3](uart-pins.jpeg "The UART Pins of the Raspberry PI 3")
Power-on your Raspberry PI, wait a couple seconds and press enter to display the OpenWRT prompt. Power-on your Raspberry PI, wait a couple seconds and press enter to display the OpenWRT prompt.

12
content/english/blog/jmeter-assess-software-performances/index.md

@ -104,7 +104,7 @@ http {
My first measure on the control (nginx) gave strange results. My first measure on the control (nginx) gave strange results.
{{< attachedFigure src="control1.png" title="First measure gave results too low to be representative of nginx's performances." >}} ![First measure gave results too low to be representative of nginx's performances.](control1.png "First measure gave results too low to be representative of nginx's performances.")
Less than 30 tps, even on an old ARM board, is definitely too low to be representative of nginx's performances. Less than 30 tps, even on an old ARM board, is definitely too low to be representative of nginx's performances.
Using [Wireshark](https://www.wireshark.org/), I discovered that JMeter did not established [Keep-Alive connections](https://sqa.stackexchange.com/questions/38211/re-using-the-tcp-connections-with-jmeter-like-a-real-browser). Using [Wireshark](https://www.wireshark.org/), I discovered that JMeter did not established [Keep-Alive connections](https://sqa.stackexchange.com/questions/38211/re-using-the-tcp-connections-with-jmeter-like-a-real-browser).
@ -137,13 +137,13 @@ httpclient.reset_state_on_thread_group_iteration=false
With Keep-Alive enabled, the 1200 tps are much more inline with the known performance level of nginx. With Keep-Alive enabled, the 1200 tps are much more inline with the known performance level of nginx.
Except there is a performance drop every 10-15 seconds that is not expected. Except there is a performance drop every 10-15 seconds that is not expected.
{{< attachedFigure src="control2.png" title="Second measure shows a periodic performance drop." >}} ![Second measure shows a periodic performance drop.](control2.png "Second measure shows a periodic performance drop.")
Using Wireshark, I discovered that during those performance drops, there are [TCP packets retransmissions](https://wiki.wireshark.org/DuplicatePackets). Using Wireshark, I discovered that during those performance drops, there are [TCP packets retransmissions](https://wiki.wireshark.org/DuplicatePackets).
At that time, my Macbook Pro hosting the injector was connected **using Wifi**. At that time, my Macbook Pro hosting the injector was connected **using Wifi**.
I switched to a good old Ethernet cable, and this time the results on the control were as expected. I switched to a good old Ethernet cable, and this time the results on the control were as expected.
{{< attachedFigure src="control3.png" title="Third measure is ok." >}} ![Third measure is ok.](control3.png "Third measure is ok.")
As a conclusion, always have a control in your experience! As a conclusion, always have a control in your experience!
@ -151,7 +151,7 @@ As a conclusion, always have a control in your experience!
To build my JMeter Test Plan, I started by adding a **User Defined Variables** component that holds all the settings related to lab environment (DNS names, ports, tokens, etc.). To build my JMeter Test Plan, I started by adding a **User Defined Variables** component that holds all the settings related to lab environment (DNS names, ports, tokens, etc.).
{{< attachedFigure src="udv.png" title="JMeter User Defined Variables" >}} ![JMeter User Defined Variables](udv.png "JMeter User Defined Variables")
I added a variable named **scenario** whose value will be passed to JMeter from the CLI. I added a variable named **scenario** whose value will be passed to JMeter from the CLI.
This enables me to run all my experiments automatically from a script, one after another. This enables me to run all my experiments automatically from a script, one after another.
@ -163,12 +163,12 @@ ${__P(parameter-name,default-value)}
I configured a **Thread Group** based on the **jp@gc - Ultimate Thread Group** with five concurrent users. I configured a **Thread Group** based on the **jp@gc - Ultimate Thread Group** with five concurrent users.
{{< attachedFigure src="thread-group.png" title="JMeter Thread Group" >}} ![JMeter Thread Group](thread-group.png "JMeter Thread Group")
And finally, a **Loop** component with three **If Controllers** underneath. And finally, a **Loop** component with three **If Controllers** underneath.
Each **If Controller** holds an **HTTP Probe** configured for the target scenario. Each **If Controller** holds an **HTTP Probe** configured for the target scenario.
{{< attachedFigure src="if.png" title="JMeter If Controller" >}} ![JMeter If Controller](if.png "JMeter If Controller")
The If Controllers are defined with a **jexl3** expression, asserting the value of the **scenario** variable. The If Controllers are defined with a **jexl3** expression, asserting the value of the **scenario** variable.

2
content/english/blog/nginx-with-tls-on-openwrt/index.md

@ -87,7 +87,7 @@ Enter **Global Build Settings** and:
* Press space to unset **Select all userspace packages by default** * Press space to unset **Select all userspace packages by default**
* Leave **Cryptographically sign packages** set * Leave **Cryptographically sign packages** set
{{< attachedFigure src="make-menuconfig.png" title="Parameters to activate in your make menuconfig" >}} ![Parameters to activate in your make menuconfig](make-menuconfig.png "Parameters to activate in your make menuconfig")
Go back to the root menu. Go back to the root menu.

6
content/english/blog/use-ansible-to-manage-the-qos-of-your-openshift-workload/index.md

@ -168,7 +168,7 @@ in order to bring back those objects to the Best Effort QoS class.
Since I do not want all Pods to have the Best Effort QoS class, I added a Since I do not want all Pods to have the Best Effort QoS class, I added a
blacklist of critical namespaces that should not be touched. blacklist of critical namespaces that should not be touched.
{{< highlight raw "hl_lines=5-10 23" >}} ```raw {hl_lines="5-10 23"}
- name: Change the QoS class of commodity projects - name: Change the QoS class of commodity projects
hosts: localhost hosts: localhost
gather_facts: no gather_facts: no
@ -192,8 +192,8 @@ blacklist of critical namespaces that should not be touched.
loop_control: loop_control:
loop_var: obj loop_var: obj
when: obj.namespace not in namespace_blacklist when: obj.namespace not in namespace_blacklist
{{< / highlight >}} ```
You can find the complete playbook [here]({{< attachedFileLink src="change-qos.yaml" >}}). Of course, it is You can find the complete playbook [here](change-qos.yaml). Of course, it is
very rough and would need to more work to be used on a daily basis but for a very rough and would need to more work to be used on a daily basis but for a
single use this is sufficient. single use this is sufficient.

22
content/english/blog/use-google-account-openid-connect-provider/index.md

@ -50,15 +50,15 @@ First, connect to the [Google Developer Console](https://console.developers.goog
Click **Create Project**. Click **Create Project**.
{{< attachedFigure src="create-project.png" title="Click 'Create Project'." >}} ![Click 'Create Project'.](create-project.png "Click 'Create Project'.")
Fill-in the **Project name** (free choice). The location does not matter. Click **Create**. Fill-in the **Project name** (free choice). The location does not matter. Click **Create**.
{{< attachedFigure src="project-name.png" title="Fill-in the 'Project name'. Click 'Create'." >}} ![Fill-in the 'Project name'. Click 'Create'.](project-name.png "Fill-in the 'Project name'. Click 'Create'.")
Click **OAuth Consent screen**. If you are a Google Suite user, select **Internal**. If you are a regular GMail user, select **External**. Click **Create**. Click **OAuth Consent screen**. If you are a Google Suite user, select **Internal**. If you are a regular GMail user, select **External**. Click **Create**.
{{< attachedFigure src="oauth-consent.png" title="The OAuth Consent screen." >}} ![The OAuth Consent screen.](oauth-consent.png "The OAuth Consent screen.")
Choose an application name (free choice). Choose an application name (free choice).
Leave the default scopes. Leave the default scopes.
@ -66,15 +66,15 @@ Add your personal domain to the list of **Authorized Domains**.
For instance, if your target service is at *raspberry-pi.example.test*, add **example.test**. For instance, if your target service is at *raspberry-pi.example.test*, add **example.test**.
**DO NOT FORGET to press Enter!** **DO NOT FORGET to press Enter!**
{{< attachedFigure src="authorized-domains.png" title="Add your personal domain to the list of Authorized Domains." >}} ![Add your personal domain to the list of Authorized Domains.](authorized-domains.png "Add your personal domain to the list of Authorized Domains.")
Fill-in the **Application Homepage Link** and **Application Privacy Policy Link** (free choices). Click **Save**. Fill-in the **Application Homepage Link** and **Application Privacy Policy Link** (free choices). Click **Save**.
{{< attachedFigure src="links.png" title="Fill-in the links." >}} ![Fill-in the links.](links.png "Fill-in the links.")
Click **Credentials**. Select **+ Create Credentials**, then **OAuth Client ID**. Click **Credentials**. Select **+ Create Credentials**, then **OAuth Client ID**.
{{< attachedFigure src="create-credentials.png" title="Create the OAuth credentials." >}} ![Create the OAuth credentials.](create-credentials.png "Create the OAuth credentials.")
Under **Application type**, select **Web Application**. Under **Application type**, select **Web Application**.
Choose a name for your application (free choice). Choose a name for your application (free choice).
@ -86,7 +86,7 @@ To be able to do so, we need to add a special Redirect URI: **http://localhost:6
Click **Create**. Click **Create**.
{{< attachedFigure src="redirect-uri.png" title="Fill-in the Redirect URI." >}} ![Fill-in the Redirect URI.](redirect-uri.png "Fill-in the Redirect URI.")
Google generated a **Client ID** and **Client Secret** for you. Keep them somewhere safe! Google generated a **Client ID** and **Client Secret** for you. Keep them somewhere safe!
@ -116,15 +116,15 @@ Now, run this script!
The script generates a URL that you need to copy and paste in your web browser. The script generates a URL that you need to copy and paste in your web browser.
{{< attachedFigure src="script-start.png" title="The script generates a URL that you need to copy and paste in your web browser." >}} ![The script generates a URL that you need to copy and paste in your web browser.](script-start.png "The script generates a URL that you need to copy and paste in your web browser.")
If you are not yet logged in, Google asks you to authenticate. If you are not yet logged in, Google asks you to authenticate.
{{< attachedFigure src="auth.png" title="If you are not yet logged in, Google asks you to authenticate." >}} ![If you are not yet logged in, Google asks you to authenticate.](auth.png "If you are not yet logged in, Google asks you to authenticate.")
Once logged in or if you are already logged in, you are redirected to the fake Redirect URI we registered earlier. Once logged in or if you are already logged in, you are redirected to the fake Redirect URI we registered earlier.
{{< attachedFigure src="auth-ok.png" title="You are redirected to the fake Redirect URI we registered earlier" >}} ![You are redirected to the fake Redirect URI we registered earlier](auth-ok.png "You are redirected to the fake Redirect URI we registered earlier")
We registered a fake Redirect URI so that we could play each part of the OpenID Connect exchange manually. We registered a fake Redirect URI so that we could play each part of the OpenID Connect exchange manually.
**So, if you see an error message from your web browser saying that it cannot connect to the target service: THIS IS EXPECTED FOR OUR TEST.** **So, if you see an error message from your web browser saying that it cannot connect to the target service: THIS IS EXPECTED FOR OUR TEST.**
@ -134,7 +134,7 @@ Once the browser is redirected at `http://localhost:666/stop-here`, copy the red
Remember that the Authorization Code is very short lived. Remember that the Authorization Code is very short lived.
So, be quick! So, be quick!
{{< attachedFigure src="script-url.png" title="Paste the Redirect URI." >}} ![Paste the Redirect URI.](script-url.png "Paste the Redirect URI.")
The script contacts the Authorization Server to get an Access Token from the Authorization Code captured in the Redirect URI. The script contacts the Authorization Server to get an Access Token from the Authorization Code captured in the Redirect URI.

64
content/english/blog/use-qlkube-to-query-the-kubernetes-api.md

@ -16,34 +16,34 @@ It is very useful for mobile application and web development: by reducing the nu
To install QLKube in OpenShift, use the NodeJS Source-to-Image builder: To install QLKube in OpenShift, use the NodeJS Source-to-Image builder:
{{< highlight sh >}} ```sh
oc new-project qlkube --display-name=QLKube oc new-project qlkube --display-name=QLKube
oc new-app nodejs~https://github.com/qlkube/qlkube.git --name=qlkube oc new-app nodejs~https://github.com/qlkube/qlkube.git --name=qlkube
{{< / highlight >}} ```
Disable TLS certificate validation to accommodate your self-signed certificates: Disable TLS certificate validation to accommodate your self-signed certificates:
{{< highlight sh >}} ```sh
oc set env dc/qlkube NODE_TLS_REJECT_UNAUTHORIZED=0 oc set env dc/qlkube NODE_TLS_REJECT_UNAUTHORIZED=0
{{< / highlight >}} ```
And enable the NodeJS development mode to enable the GraphQL explorer (disabled in production mode): And enable the NodeJS development mode to enable the GraphQL explorer (disabled in production mode):
{{< highlight sh >}} ```sh
oc set env dc/qlkube NODE_ENV=development oc set env dc/qlkube NODE_ENV=development
{{< / highlight >}} ```
Give the GLKube's Service Account the right to query the Kubernetes API for its own namespace: Give the GLKube's Service Account the right to query the Kubernetes API for its own namespace:
{{< highlight sh >}} ```sh
oc adm policy add-role-to-user view -z default oc adm policy add-role-to-user view -z default
{{< / highlight >}} ```
Once deployed, open the QLKube URL in your web browser: Once deployed, open the QLKube URL in your web browser:
{{< highlight sh >}} ```sh
open $(oc get route qlkube -o go-template --template="http://{{.spec.host}}") open $(oc get route qlkube -o go-template --template="http://{{.spec.host}}")
{{< / highlight >}} ```
You can try the following queries in the GraphQL explorer. You can try the following queries in the GraphQL explorer.
@ -53,7 +53,7 @@ Unless you gave the `cluster-admin` right to the QLKube Service Account, you wil
**Query:** **Query:**
{{< highlight graphql >}} ```graphql
query getAllPodsInCurrentNamespace { query getAllPodsInCurrentNamespace {
all(namespace: "qlkube") { all(namespace: "qlkube") {
pods { pods {
@ -69,11 +69,11 @@ query getAllPodsInCurrentNamespace {
} }
} }
} }
{{< / highlight >}} ```
**Response:** **Response:**
{{< highlight json >}} ```json
{ {
"data": { "data": {
"all": { "all": {
@ -102,7 +102,7 @@ query getAllPodsInCurrentNamespace {
} }
} }
} }
{{< / highlight >}} ```
## Get a service by name ## Get a service by name
@ -110,7 +110,7 @@ To get an object by name, you can use the `fieldSelector` parameter (in this exa
**Query:** **Query:**
{{< highlight graphql >}} ```graphql
query getServiceByNameAndNamespace { query getServiceByNameAndNamespace {
all(namespace: "qlkube", fieldSelector: "metadata.name=qlkube") { all(namespace: "qlkube", fieldSelector: "metadata.name=qlkube") {
services { services {
@ -126,11 +126,11 @@ query getServiceByNameAndNamespace {
} }
} }
} }
{{< / highlight >}} ```
**Response:** **Response:**
{{< highlight json >}} ```json
{ {
"data": { "data": {
"all": { "all": {
@ -150,14 +150,14 @@ query getServiceByNameAndNamespace {
} }
} }
} }
{{< / highlight >}} ```
## Type introspection ## Type introspection
Playing with the built-in types of GLKube is nice but you might soon be limited. Playing with the built-in types of GLKube is nice but you might soon be limited.
To discover all the available types, run this query: To discover all the available types, run this query:
{{< highlight graphql >}} ```graphql
{ {
__schema { __schema {
types { types {
@ -165,11 +165,11 @@ To discover all the available types, run this query:
} }
} }
} }
{{< / highlight >}} ```
This query returns a list of all the available types (truncated here for brevity): This query returns a list of all the available types (truncated here for brevity):
{{< highlight json >}} ```json
{ {
"data": { "data": {
"__schema": { "__schema": {
@ -199,7 +199,7 @@ This query returns a list of all the available types (truncated here for brevity
} }
} }
} }
{{< / highlight >}} ```
## Get a Deployment Config by name and namespace ## Get a Deployment Config by name and namespace
@ -210,7 +210,7 @@ Once the desired data type discovered, you can use it directly.
**Query:** **Query:**
{{< highlight graphql >}} ```graphql
query getDeploymentConfigByNameAndNamespace { query getDeploymentConfigByNameAndNamespace {
comGithubOpenshiftApiAppsV1DeploymentConfig(name: "qlkube", namespace: "qlkube") { comGithubOpenshiftApiAppsV1DeploymentConfig(name: "qlkube", namespace: "qlkube") {
metadata { metadata {
@ -222,11 +222,11 @@ query getDeploymentConfigByNameAndNamespace {
} }
} }
} }
{{< / highlight >}} ```
**Reponse:** **Reponse:**
{{< highlight json >}} ```json
{ {
"data": { "data": {
"comGithubOpenshiftApiAppsV1DeploymentConfig": { "comGithubOpenshiftApiAppsV1DeploymentConfig": {
@ -240,7 +240,7 @@ query getDeploymentConfigByNameAndNamespace {
} }
} }
} }
{{< / highlight >}} ```
## Get routes by hostname and namespace ## Get routes by hostname and namespace
@ -248,7 +248,7 @@ This query use a `fieldSelector` on the `host` field in the `spec` section and u
**Query:** **Query:**
{{< highlight graphql >}} ```graphql
query getRouteByHostnameAndNamespace { query getRouteByHostnameAndNamespace {
routes: comGithubOpenshiftApiRouteV1RouteList(namespace: "qlkube" fieldSelector: "spec.host=qlkube-qlkube.app.itix.fr") { routes: comGithubOpenshiftApiRouteV1RouteList(namespace: "qlkube" fieldSelector: "spec.host=qlkube-qlkube.app.itix.fr") {
items { items {
@ -266,11 +266,11 @@ query getRouteByHostnameAndNamespace {
} }
} }
} }
{{< / highlight >}} ```
**Reponse:** **Reponse:**
{{< highlight json >}} ```json
{ {
"data": { "data": {
"routes": { "routes": {
@ -296,13 +296,13 @@ query getRouteByHostnameAndNamespace {
} }
} }
} }
{{< / highlight >}} ```
## Sending your GraphQL request from curl ## Sending your GraphQL request from curl
Once your GraphQL queries refined in the GraphQL Explorer, you can send them directly using curl or any HTTP client. Once your GraphQL queries refined in the GraphQL Explorer, you can send them directly using curl or any HTTP client.
{{< highlight sh >}} ```sh
export GLKUBE_HOSTNAME=$(oc get route qlkube -o go-template --template="{{.spec.host}}") export GLKUBE_HOSTNAME=$(oc get route qlkube -o go-template --template="{{.spec.host}}")
cat <<EOF | curl -XPOST "http://$GLKUBE_HOSTNAME/" -H "Content-Type: application/json" -d @- -s |jq . cat <<EOF | curl -XPOST "http://$GLKUBE_HOSTNAME/" -H "Content-Type: application/json" -d @- -s |jq .
@ -323,7 +323,7 @@ cat <<EOF | curl -XPOST "http://$GLKUBE_HOSTNAME/" -H "Content-Type: application
}" }"
} }
EOF EOF
{{< / highlight >}} ```
## Advanced use-cases ## Advanced use-cases

8
content/english/blog/writing-workshop-instructions-with-hugo-deploy-openshift/index.md

@ -168,25 +168,25 @@ First, get the GitHub Webhook URL of your BuildConfig:
- Open the **Configuration** tab - Open the **Configuration** tab
- Copy the **GitHub Webhook URL** - Copy the **GitHub Webhook URL**
{{< attachedFigure src="openshift-buildconfig-webhook.png" title="The BuildConfig on OpenShift." >}} ![The BuildConfig on OpenShift.](openshift-buildconfig-webhook.png "The BuildConfig on OpenShift.")
Then, go on your GitHub repository and add a webhook: Then, go on your GitHub repository and add a webhook:
- Go to **Settings** > **Webhooks** - Go to **Settings** > **Webhooks**
- Click **Add webhook** - Click **Add webhook**
{{< attachedFigure src="github-add-webhook.png" title="Add a webhook on GitHub." >}} ![Add a webhook on GitHub.](github-add-webhook.png "Add a webhook on GitHub.")
- Paste your Webhook URL in the **Payload URL** field - Paste your Webhook URL in the **Payload URL** field
- Select `application/json` in the **Content type** dropdown list - Select `application/json` in the **Content type** dropdown list
- Check **Disable** in the **SSL verification** section if your OpenShift console has a self-signed certificate - Check **Disable** in the **SSL verification** section if your OpenShift console has a self-signed certificate
- Click **Add webhook** - Click **Add webhook**
{{< attachedFigure src="github-webhook.png" title="The GitHub webhook." >}} ![The GitHub webhook.](github-webhook.png "The GitHub webhook.")
Try to push some changes to your GitHub repository and see OpenShift rebuilding and deploying your website! Try to push some changes to your GitHub repository and see OpenShift rebuilding and deploying your website!
{{< attachedFigure src="openshift-rebuild.png" title="OpenShift rebuilding and redeploying your website" >}} ![OpenShift rebuilding and redeploying your website](openshift-rebuild.png "OpenShift rebuilding and redeploying your website")
## Conclusion ## Conclusion

2
content/english/blog/writing-workshop-instructions-with-hugo/index.md

@ -201,7 +201,7 @@ git push -u origin master
Congratulations! You now have your training instructions neatly organised and Congratulations! You now have your training instructions neatly organised and
clearly presented. Maintenance and collaboration have been greatly simplified! clearly presented. Maintenance and collaboration have been greatly simplified!
{{< attachedFigure src="hugo-screenshot.png" title="Screenshot of our mini-training." >}} ![Screenshot of our mini-training.](hugo-screenshot.png "Screenshot of our mini-training.")
In this first part of the series, we presented a very light introduction to In this first part of the series, we presented a very light introduction to
Hugo and its application to workshop instructions. Be sure to read [part 2: Writing workshop instructions with Hugo, with variables in your content](../writing-workshop-instructions-with-hugo-variables/) to discover advanced usages. Hugo and its application to workshop instructions. Be sure to read [part 2: Writing workshop instructions with Hugo, with variables in your content](../writing-workshop-instructions-with-hugo-variables/) to discover advanced usages.

1
content/english/opensource/_index.md

@ -1,5 +1,6 @@
--- ---
title: "Open Source" title: "Open Source"
description: "The Open Source projects covered on this website: OpenShift, Keycloak, 3scale, Podman, Ansible, and more."
--- ---
In my daily work I use many Open Source communities, on which I have written articles. In my daily work I use many Open Source communities, on which I have written articles.

4
content/english/search/index.md

@ -2,4 +2,8 @@
title: "Search" title: "Search"
type: search type: search
layout: search layout: search
# The search index is generated as a separate JSON file (see config.yaml)
outputs:
- html
- searchindex
--- ---

1
content/english/speaking/_index.md

@ -1,5 +1,6 @@
--- ---
title: "Speaking" title: "Speaking"
description: "Talks, workshops and demos given by Nicolas Massé at conferences and customer events."
--- ---
I am occasionally invited to customers or corporate events to talk about APIs and SSO. I am occasionally invited to customers or corporate events to talk about APIs and SSO.

4
content/english/speaking/apidays-2017/index.md

@ -9,7 +9,7 @@ draft: false
On the 30th January 2018, I spoke at API Days on a session named On the 30th January 2018, I spoke at API Days on a session named
_"An API Journey: from Mock to Deployment workshop"_. _"An API Journey: from Mock to Deployment workshop"_.
{{< screenshotOf src="tweet-jylls35.png" href="https://twitter.com/jylls35/status/958303326638141440" >}} [![Tweet by @jylls35](tweet-jylls35.png)](https://twitter.com/jylls35/status/958303326638141440)
{{< screenshotOf src="tweet-RedHatFrance.png" href="https://twitter.com/RedHatFrance/status/958633464932241408" >}} [![Tweet by @RedHatFrance](tweet-RedHatFrance.png)](https://twitter.com/RedHatFrance/status/958633464932241408)
For more information: http://www.apidays.io/events/paris-2017 For more information: http://www.apidays.io/events/paris-2017

12
content/english/speaking/devoteam-tech-for-people-2018/index.md

@ -10,7 +10,11 @@ The 10th Septembre 2018, I co-presented a session named _"No API, No Future"_
in which I exhibited the Red Hat's API Lifecycle Automation. The event in which I exhibited the Red Hat's API Lifecycle Automation. The event
`#TechForPeople` was organized by Devoteam. `#TechForPeople` was organized by Devoteam.
{{< attachedFigure src="2018-10-10-Devoteam-TechForPeople-1.jpg" >}} ![](2018-10-10-Devoteam-TechForPeople-1.jpg)
{{< attachedFigure src="2018-10-10-Devoteam-TechForPeople-2.jpg" >}}
{{< attachedFigure src="2018-10-10-Devoteam-TechForPeople-3.jpg" >}} ![](2018-10-10-Devoteam-TechForPeople-2.jpg)
{{< attachedFigure src="2018-10-10-Devoteam-TechForPeople-4.jpg" >}}
![](2018-10-10-Devoteam-TechForPeople-3.jpg)
![](2018-10-10-Devoteam-TechForPeople-4.jpg)

21
content/english/speaking/devoxx-france-2019/index.md

@ -18,18 +18,21 @@ On the 19th April 2019, I spoke at Devoxx France on a session named _"Une API, d
## Pictures ## Pictures
{{< attachedFigure src="on-stage-1.jpg" >}} ![](on-stage-1.jpg)
{{< attachedFigure src="on-stage-2.jpg" >}}
{{< attachedFigure src="on-stage-4.jpg" >}} ![](on-stage-2.jpg)
{{< attachedFigure src="on-stage-5.jpg" >}}
![](on-stage-4.jpg)
![](on-stage-5.jpg)
## Attendees' reaction ## Attendees' reaction
{{< screenshotOf src="tweet-lbroudoux.png" href="https://twitter.com/lbroudoux/status/1119221025466720256" >}} [![Tweet by @lbroudoux](tweet-lbroudoux.png)](https://twitter.com/lbroudoux/status/1119221025466720256)
{{< screenshotOf src="tweet-gbloquel.png" href="https://twitter.com/gbloquel/status/1119170710696493056" >}} [![Tweet by @gbloquel](tweet-gbloquel.png)](https://twitter.com/gbloquel/status/1119170710696493056)
{{< screenshotOf src="tweet-sebi2706.png" href="https://twitter.com/sebi2706/status/1119169813392314368" >}} [![Tweet by @sebi2706](tweet-sebi2706.png)](https://twitter.com/sebi2706/status/1119169813392314368)
{{< screenshotOf src="tweet-cgodard.png" href="https://twitter.com/cgodard/status/1119159971835289600" >}} [![Tweet by @cgodard](tweet-cgodard.png)](https://twitter.com/cgodard/status/1119159971835289600)
{{< screenshotOf src="tweet-PetitMel_issa.png" href="https://twitter.com/PetitMel_issa/status/1119170065289678848" >}} [![Tweet by @PetitMel_issa](tweet-PetitMel_issa.png)](https://twitter.com/PetitMel_issa/status/1119170065289678848)
For more information: https://cfp.devoxx.fr/2019/talk/OWQ-3436/Une_API,_de_l'idee_a_la_production,_en_mode_agile_avec_Red_Hat_! For more information: https://cfp.devoxx.fr/2019/talk/OWQ-3436/Une_API,_de_l'idee_a_la_production,_en_mode_agile_avec_Red_Hat_!

2
content/english/speaking/red-hat-forum-2017/index.md

@ -11,6 +11,6 @@ _"Gestion des API dans une démarche CI/CD"_.
For more information: https://www.redhat.com/fr/events/red-hat-forum-france-2017 For more information: https://www.redhat.com/fr/events/red-hat-forum-france-2017
{{< screenshotOf src="tweet-ylebihan.png" href="https://twitter.com/ylebihan/status/918126983963447296" >}} [![Tweet by @ylebihan](tweet-ylebihan.png)](https://twitter.com/ylebihan/status/918126983963447296)
[Slide deck can be found here](http://images.engage.redhat.com/Web/RedHat/%7Bd7510765-62e8-4ea8-9f64-226259730a17%7D_Gestion_des_API_dans_une_de%CC%81marche_CI_CD.pdf) [Slide deck can be found here](http://images.engage.redhat.com/Web/RedHat/%7Bd7510765-62e8-4ea8-9f64-226259730a17%7D_Gestion_des_API_dans_une_de%CC%81marche_CI_CD.pdf)

10
content/english/speaking/red-hat-open-demo-build-multi-architecture-ci-cd-pipelines-to-run-your-apps-in-the-cloud-and-at-the-edge/index.md

@ -21,7 +21,7 @@ On September 5, 2024, I presented a webinar named [Red Hat Open Demo-Build multi
In the rapidly advancing field of edge computing, deploying applications across diverse hardware platforms, such as ARM and x86_64, has become essential. In the rapidly advancing field of edge computing, deploying applications across diverse hardware platforms, such as ARM and x86_64, has become essential.
Multi-architecture container images have emerged as a powerful solution, supporting multiple processor architectures within a single image package and simplifying the deployment process across platforms. Multi-architecture container images have emerged as a powerful solution, supporting multiple processor architectures within a single image package and simplifying the deployment process across platforms.
{{< attachedFigure src="slide-arm-devices.png" >}} ![](slide-arm-devices.png)
During this demonstration, I explored how these multi-architecture images work seamlessly across different CPU architectures, automatically selecting the appropriate client architecture from a registry. During this demonstration, I explored how these multi-architecture images work seamlessly across different CPU architectures, automatically selecting the appropriate client architecture from a registry.
Using tools like Podman, Buildah, and Tekton, I showcased how easy it is to build these images. Using tools like Podman, Buildah, and Tekton, I showcased how easy it is to build these images.
@ -40,7 +40,7 @@ Here’s what was covered during the demo:
- **Tekton Task Binding to the Right Node**: I also demonstrated how Tekton tasks could be directed to specific nodes based on architecture, ensuring efficient execution across mixed environments. - **Tekton Task Binding to the Right Node**: I also demonstrated how Tekton tasks could be directed to specific nodes based on architecture, ensuring efficient execution across mixed environments.
{{< attachedFigure src="slide-architecture.png" >}} ![](slide-architecture.png)
Key Highlights from the Demo: Key Highlights from the Demo:
@ -50,11 +50,11 @@ Key Highlights from the Demo:
3. **Building and Pushing Multi-Architecture Container Images to quay.io**: I concluded with a real-time demonstration of building and pushing multi-architecture images to the quay.io registry, emphasizing the role of registry support in deploying cross-platform applications efficiently. 3. **Building and Pushing Multi-Architecture Container Images to quay.io**: I concluded with a real-time demonstration of building and pushing multi-architecture images to the quay.io registry, emphasizing the role of registry support in deploying cross-platform applications efficiently.
4. **Running the same container on two different CPU architectures**: I ran a container image built with the multi-architecture pipeline on both my Laptop (x86_64) and {{< internalLink path="/blog/homelab-server-2u-short-depth-front-io-ampere-altra-arm64-architecture/index.md" title="my Ampere Altra server" >}} (ARM64). 4. **Running the same container on two different CPU architectures**: I ran a container image built with the multi-architecture pipeline on both my Laptop (x86_64) and [my Ampere Altra server](/blog/homelab-server-2u-short-depth-front-io-ampere-altra-arm64-architecture/index.md) (ARM64).
This demo has been designed for DevOps professionals, cloud architects, and developers looking to leverage OpenShift and AWS in multi-architecture container image creation. This demo has been designed for DevOps professionals, cloud architects, and developers looking to leverage OpenShift and AWS in multi-architecture container image creation.
The session provided them with both a high-level understanding and practical skills to implement and manage these capabilities in their environments. The session provided them with both a high-level understanding and practical skills to implement and manage these capabilities in their environments.
If you have not been able to attend the live session, I invite you to [watch the replay](https://events.redhat.com/profile/form/index.cfm?PKformID=0x11759490001) and [download the slides]({{< attachedFileLink src="slides.pdf" >}})! If you have not been able to attend the live session, I invite you to [watch the replay](https://events.redhat.com/profile/form/index.cfm?PKformID=0x11759490001) and [download the slides](slides.pdf)!
If you are ready to dive deeper, have a look at the article I wrote on this subject: {{< internalLink path="/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-aws/index.md" >}}! If you are ready to dive deeper, have a look at the article I wrote on this subject: [](/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-aws/index.md)!

10
content/english/speaking/red-hat-open-demo-from-the-esp8266-to-the-openshift-cluster-discover-what-red-hat-can-offer-for-the-edge-computing/index.md

@ -15,7 +15,7 @@ topics:
--- ---
On April 4, 2023, I presented a webinar named [From the ESP8266 to the OpenShift cluster: discover what Red Hat can offer for the Edge computing!](https://events.redhat.com/profile/form/index.cfm?PKformID=0x1270056abcd&extIdCarryOver=true&sc_cid=701f2000000txokAAA) with three colleagues: [Adrien](https://www.linkedin.com/in/adrien-legros-78674a133/), [Florian](https://www.linkedin.com/in/florian-even/) and [Sébastien](https://www.linkedin.com/in/sebastien-lallemand/). On April 4, 2023, I presented a webinar named [From the ESP8266 to the OpenShift cluster: discover what Red Hat can offer for the Edge computing!](https://events.redhat.com/profile/form/index.cfm?PKformID=0x1270056abcd&extIdCarryOver=true&sc_cid=701f2000000txokAAA) with three colleagues: [Adrien](https://www.linkedin.com/in/adrien-legros-78674a133/), [Florian](https://www.linkedin.com/in/florian-even/) and [Sébastien](https://www.linkedin.com/in/sebastien-lallemand/).
This webinar showcases a lab that we built and delivered for the {{< internalLink path="/speaking/red-hat-tech-exchange-2023/index.md" >}}. This webinar showcases a lab that we built and delivered for the [](/speaking/red-hat-tech-exchange-2023/index.md).
<!--more--> <!--more-->
@ -26,13 +26,13 @@ The exercises revolved around the common and relatable theme of "shipment tracki
Participants helped a fictious company, FSC (the **F**edora **S**hipping **C**ompany), set up its Edge computing infrastructure to support 10 warehouses and a central headquarter across EMEA. Participants helped a fictious company, FSC (the **F**edora **S**hipping **C**ompany), set up its Edge computing infrastructure to support 10 warehouses and a central headquarter across EMEA.
{{< attachedFigure src="slide-context.png" >}} ![](slide-context.png)
During the lab, attendees set up and deployed all essential components to create a connected parcel tracking system. During the lab, attendees set up and deployed all essential components to create a connected parcel tracking system.
They began by configuring devices, using the Arduino IDE and an [ESP8266](https://en.wikipedia.org/wiki/ESP8266), to read the parcel tracking number using RFIDs, which is then transmitted over Wi-Fi to an MQTT broker. They began by configuring devices, using the Arduino IDE and an [ESP8266](https://en.wikipedia.org/wiki/ESP8266), to read the parcel tracking number using RFIDs, which is then transmitted over Wi-Fi to an MQTT broker.
Using Camel-K, the data was transformed into actionable events and sent to a central headquarter system for real-time reporting. Using Camel-K, the data was transformed into actionable events and sent to a central headquarter system for real-time reporting.
{{< attachedFigure src="architecture.svg" >}} ![](architecture.svg)
Meanwhile, at the headquarter level, a dedicated application visually mapped the movement of parcels as they traveled between hubs, creating an interactive, dynamic view of the entire shipment process. Meanwhile, at the headquarter level, a dedicated application visually mapped the movement of parcels as they traveled between hubs, creating an interactive, dynamic view of the entire shipment process.
@ -44,7 +44,7 @@ To make the lab as much fun and interactive as possible, the participants had to
- Two people, *ditto*, were setting up the firmware of the ESP8266 microcontrollers to scan **outgoing** parcels, along with the MQTT broker and the **outgoing** topic. - Two people, *ditto*, were setting up the firmware of the ESP8266 microcontrollers to scan **outgoing** parcels, along with the MQTT broker and the **outgoing** topic.
- Two people, *ditto*, were setting up the Camel-K routes and the Kafka broker. - Two people, *ditto*, were setting up the Camel-K routes and the Kafka broker.
{{< attachedFigure src="slide-principle.png" >}} ![](slide-principle.png)
Then, once everything properly setup, a participant scan a parcel in the **Athens** warehouse and then in the **Paris** warehouse and in realtime a parcel would move from **Athens** to **Paris** on the map displayed on the screen. Then, once everything properly setup, a participant scan a parcel in the **Athens** warehouse and then in the **Paris** warehouse and in realtime a parcel would move from **Athens** to **Paris** on the map displayed on the screen.
@ -65,7 +65,7 @@ Rince and repeat for the 25 units!
For each unit, it took me from one hour down to 20 minutes once properly trained. For each unit, it took me from one hour down to 20 minutes once properly trained.
Quite a time consuming task! Quite a time consuming task!
{{< attachedFigure src="esp8266-rc522.jpeg" >}} ![](esp8266-rc522.jpeg)
## Replay ## Replay

14
content/english/speaking/red-hat-open-demo-mission-impossible-stop-the-crazy-train-with-ai-and-edge-before-it-is-too-late/index.md

@ -23,16 +23,16 @@ In this article, I will give you an overview of the demo and you will be able to
## The "Mission impossible" demo ## The "Mission impossible" demo
We designed this demo for the {{< internalLink path="/speaking/platform-day-2024/index.md" >}} event based on the latest opus of the movie **Mission Impossible: Dead Reckoning**. We designed this demo for the [](/speaking/platform-day-2024/index.md) event based on the latest opus of the movie **Mission Impossible: Dead Reckoning**.
In this demo, **Ethan Hunt** needs help to stop the **Lego City #60337** train before it's too late! In this demo, **Ethan Hunt** needs help to stop the **Lego City #60337** train before it's too late!
Nothing less than the fate of humanity is at stake! Nothing less than the fate of humanity is at stake!
{{< attachedFigure src="mission-impossible-plot.png" >}} ![](mission-impossible-plot.png)
The scenario requires **Ethan Hunt** to board the train to connect a **Nvidia Jetson Orin Nano** card to the train's computer network and deploy an AI that will recognise the traffic signs and stop the train on time before it derails! The scenario requires **Ethan Hunt** to board the train to connect a **Nvidia Jetson Orin Nano** card to the train's computer network and deploy an AI that will recognise the traffic signs and stop the train on time before it derails!
A console will provide a remote view of the train's video surveillance camera, with the results of the AI model's inference overlaid. A console will provide a remote view of the train's video surveillance camera, with the results of the AI model's inference overlaid.
{{< attachedFigure src="mission-impossible-scenario.png" >}} ![](mission-impossible-scenario.png)
To run this demo, we equipped the **Lego** train with a **Nvidia Jetson Orin Nano** card, a webcam and a portable battery. To run this demo, we equipped the **Lego** train with a **Nvidia Jetson Orin Nano** card, a webcam and a portable battery.
The Nvidia Jetson Orin card is a System On Chip (SoC), it includes all the hardware that **Ethan Hunt** needs for its mission: CPU, RAM, storage... The Nvidia Jetson Orin card is a System On Chip (SoC), it includes all the hardware that **Ethan Hunt** needs for its mission: CPU, RAM, storage...
@ -40,7 +40,7 @@ Plus a GPU to speed up the calculations!
The Jetson receives the video stream from the onboard camera and transmits orders to the **Lego** Hub via the **Bluetooth Low Energy** protocol. The Jetson receives the video stream from the onboard camera and transmits orders to the **Lego** Hub via the **Bluetooth Low Energy** protocol.
It is powered by a portable battery for the duration of the mission. It is powered by a portable battery for the duration of the mission.
{{< attachedFigure src="rhel-booth-mission-impossible-demo.jpeg" >}} ![](rhel-booth-mission-impossible-demo.jpeg)
We are in an Edge Computing context. We are in an Edge Computing context.
On the Jetson, we have installed **Red Hat Device Edge**. On the Jetson, we have installed **Red Hat Device Edge**.
@ -55,7 +55,7 @@ The video stream is broadcast from the Jetson via a **Kafka broker**!
On top of this, there are MLops pipelines to train the AI model. On top of this, there are MLops pipelines to train the AI model.
And finally CI/CD pipelines to build the container images of our microservices for x86 and ARM architectures. And finally CI/CD pipelines to build the container images of our microservices for x86 and ARM architectures.
{{< attachedFigure src="mission-impossible-hardware-architecture.png" >}} ![](mission-impossible-hardware-architecture.png)
To enable **Ethan Hunt** to carry out its mission successfully, we had to guarantee end-to-end data transmission. To enable **Ethan Hunt** to carry out its mission successfully, we had to guarantee end-to-end data transmission.
To do this, we implemented five services that communicate via an asynchronous message transmission system (**MQTT**). To do this, we implemented five services that communicate via an asynchronous message transmission system (**MQTT**).
@ -65,7 +65,7 @@ Each image is resized to 600x400 pixels and encapsulated in an event with a uniq
This event is transmitted to the AI model, which enriches it with the result of the prediction. This event is transmitted to the AI model, which enriches it with the result of the prediction.
The latter is transmitted to a transformation service whose role is to extract the train's action, transmit it to the train controller to slow down or stop the train and at the same time send the event to the streaming service (**Kafka**) deployed on a remote Openshift, which displays the images and the prediction in real time. The latter is transmitted to a transformation service whose role is to extract the train's action, transmit it to the train controller to slow down or stop the train and at the same time send the event to the streaming service (**Kafka**) deployed on a remote Openshift, which displays the images and the prediction in real time.
{{< attachedFigure src="mission-impossible-software-architecture.png" >}} ![](mission-impossible-software-architecture.png)
And finally, we had to build an artificial intelligence model. And finally, we had to build an artificial intelligence model.
To do this, we followed best practices for managing the model's lifecycle, known as **MLOps**: To do this, we followed best practices for managing the model's lifecycle, known as **MLOps**:
@ -78,7 +78,7 @@ To do this, we followed best practices for managing the model's lifecycle, known
- **Measure performance and re-train**: By observing the model's behaviour, we were able to measure the quality of the predictions and note that not all **Lego** panels were well recognised. - **Measure performance and re-train**: By observing the model's behaviour, we were able to measure the quality of the predictions and note that not all **Lego** panels were well recognised.
We decided to re-train the model by refining it with an enriched dataset. We decided to re-train the model by refining it with an enriched dataset.
{{< attachedFigure src="mission-impossible-ai.png" >}} ![](mission-impossible-ai.png)
## Watch the replay! ## Watch the replay!

26
content/english/speaking/red-hat-summit-connect-france-2024/index.md

@ -33,7 +33,7 @@ All while immersing them in a captivating adventure where every exercise involve
During the workshop, participants had to develop no less than four microservices to build an application simulating combat between superheroes and super villains. During the workshop, participants had to develop no less than four microservices to build an application simulating combat between superheroes and super villains.
{{< attachedFigure src="open-code-quest-microservices.png" >}} ![](open-code-quest-microservices.png)
The microservices were developed in Quarkus, the native Java framework for the cloud, demonstrating how it can transform application development by combining speed of development, lightness and performance. The microservices were developed in Quarkus, the native Java framework for the cloud, demonstrating how it can transform application development by combining speed of development, lightness and performance.
In particular, Quarkus significantly reduces the memory footprint of applications, while enabling them to start up almost instantaneously. In particular, Quarkus significantly reduces the memory footprint of applications, while enabling them to start up almost instantaneously.
@ -46,7 +46,7 @@ Based on the **Tekton Chains** and **Sigstore** technologies, this product offer
Attendees were able to discover how these tools enhance the security of deployments by providing proof of compliance and transparency on the dependencies used in applications. Attendees were able to discover how these tools enhance the security of deployments by providing proof of compliance and transparency on the dependencies used in applications.
I'll let you discover the full list of tools used in the **Open Code Quest** workshop: I'll let you discover the full list of tools used in the **Open Code Quest** workshop:
{{< attachedFigure src="open-code-quest-namespaces.png" >}} ![](open-code-quest-namespaces.png)
Before and during the **Open Code Quest**, the administration of the **platform** played a key role in the success of the event. Before and during the **Open Code Quest**, the administration of the **platform** played a key role in the success of the event.
As an organising member, I was responsible, along with [Sébastien Lallemand](https://sebastienlallemand.net/), for preparing, sizing, installing and configuring the eight OpenShift clusters needed for the workshops to run smoothly. As an organising member, I was responsible, along with [Sébastien Lallemand](https://sebastienlallemand.net/), for preparing, sizing, installing and configuring the eight OpenShift clusters needed for the workshops to run smoothly.
@ -55,7 +55,7 @@ This crucial preparation phase ensured a stable, high-performance infrastructure
During the event, my role as SRE (Site Reliability Engineer) was to closely monitor critical metrics, such as resource utilisation, to ensure a smooth and optimal experience for all participants. During the event, my role as SRE (Site Reliability Engineer) was to closely monitor critical metrics, such as resource utilisation, to ensure a smooth and optimal experience for all participants.
Thanks to this proactive monitoring, we were able to offer constant availability of the environments and thus facilitate the smooth running of the workshop. Thanks to this proactive monitoring, we were able to offer constant availability of the environments and thus facilitate the smooth running of the workshop.
{{< attachedFigure src="open-code-quest-clusters.png" >}} ![](open-code-quest-clusters.png)
Another challenge I tackled for the Open Code Quest was the creation of a **Leaderboard** designed to encourage emulation between participants. Another challenge I tackled for the Open Code Quest was the creation of a **Leaderboard** designed to encourage emulation between participants.
This project required me to think outside the box, as I had to use tools such as **Prometheus** and **Grafana** for a task they weren't designed for: sorting participants by order of finish. This project required me to think outside the box, as I had to use tools such as **Prometheus** and **Grafana** for a task they weren't designed for: sorting participants by order of finish.
@ -76,22 +76,22 @@ How have I calibrated the bonuses and accelerators to encourage competition and
Everything is explained in these two articles: Everything is explained in these two articles:
1. {{< internalLink path="/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md" >}} 1. [](/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md)
2. {{< internalLink path="/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md" >}} 2. [](/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md)
## "Mission Impossible" demo: Lego, AI & Edge Computing ## "Mission Impossible" demo: Lego, AI & Edge Computing
For part of the day, I was on the RHEL booth, accompanied by [Adrien](https://www.linkedin.com/in/adrien-legros-78674a133/), [Mourad](https://www.linkedin.com/in/mourad-ouachani-0734218/) and [Pauline](https://www.linkedin.com/in/trg-pauline/) to install the "Mission Impossible" demo and answer questions from the public. For part of the day, I was on the RHEL booth, accompanied by [Adrien](https://www.linkedin.com/in/adrien-legros-78674a133/), [Mourad](https://www.linkedin.com/in/mourad-ouachani-0734218/) and [Pauline](https://www.linkedin.com/in/trg-pauline/) to install the "Mission Impossible" demo and answer questions from the public.
We designed this demo for the {{< internalLink path="/speaking/platform-day-2024/index.md" >}} event based on the latest opus of the movie **Mission Impossible: Dead Reckoning**. We designed this demo for the [](/speaking/platform-day-2024/index.md) event based on the latest opus of the movie **Mission Impossible: Dead Reckoning**.
In this demo, **Ethan Hunt** needs help to stop the **Lego City #60337** train before it's too late! In this demo, **Ethan Hunt** needs help to stop the **Lego City #60337** train before it's too late!
Nothing less than the fate of humanity is at stake! Nothing less than the fate of humanity is at stake!
{{< attachedFigure src="mission-impossible-plot.png" >}} ![](mission-impossible-plot.png)
The scenario requires **Ethan Hunt** to board the train to connect a **Nvidia Jetson Orin Nano** card to the train's computer network and deploy an AI that will recognise the traffic signs and stop the train on time before it derails! The scenario requires **Ethan Hunt** to board the train to connect a **Nvidia Jetson Orin Nano** card to the train's computer network and deploy an AI that will recognise the traffic signs and stop the train on time before it derails!
A console will provide a remote view of the train's video surveillance camera, with the results of the AI model's inference overlaid. A console will provide a remote view of the train's video surveillance camera, with the results of the AI model's inference overlaid.
{{< attachedFigure src="mission-impossible-scenario.png" >}} ![](mission-impossible-scenario.png)
To run this demo, we equipped the **Lego** train with a **Nvidia Jetson Orin Nano** card, a webcam and a portable battery. To run this demo, we equipped the **Lego** train with a **Nvidia Jetson Orin Nano** card, a webcam and a portable battery.
The Nvidia Jetson Orin card is a System On Chip (SoC), it includes all the hardware that **Ethan Hunt** needs for its mission: CPU, RAM, storage... The Nvidia Jetson Orin card is a System On Chip (SoC), it includes all the hardware that **Ethan Hunt** needs for its mission: CPU, RAM, storage...
@ -99,7 +99,7 @@ Plus a GPU to speed up the calculations!
The Jetson receives the video stream from the onboard camera and transmits orders to the **Lego** Hub via the **Bluetooth Low Energy** protocol. The Jetson receives the video stream from the onboard camera and transmits orders to the **Lego** Hub via the **Bluetooth Low Energy** protocol.
It is powered by a portable battery for the duration of the mission. It is powered by a portable battery for the duration of the mission.
{{< attachedFigure src="rhel-booth-mission-impossible-demo.jpeg" >}} ![](rhel-booth-mission-impossible-demo.jpeg)
We are in an Edge Computing context. We are in an Edge Computing context.
On the Jetson, we have installed **Red Hat Device Edge**. On the Jetson, we have installed **Red Hat Device Edge**.
@ -114,7 +114,7 @@ The video stream is broadcast from the Jetson via a **Kafka broker**!
On top of this, there are MLops pipelines to train the AI model. On top of this, there are MLops pipelines to train the AI model.
And finally CI/CD pipelines to build the container images of our microservices for x86 and ARM architectures. And finally CI/CD pipelines to build the container images of our microservices for x86 and ARM architectures.
{{< attachedFigure src="mission-impossible-hardware-architecture.png" >}} ![](mission-impossible-hardware-architecture.png)
To enable **Ethan Hunt** to carry out its mission successfully, we had to guarantee end-to-end data transmission. To enable **Ethan Hunt** to carry out its mission successfully, we had to guarantee end-to-end data transmission.
To do this, we implemented five services that communicate via an asynchronous message transmission system (**MQTT**). To do this, we implemented five services that communicate via an asynchronous message transmission system (**MQTT**).
@ -124,7 +124,7 @@ Each image is resized to 600x400 pixels and encapsulated in an event with a uniq
This event is transmitted to the AI model, which enriches it with the result of the prediction. This event is transmitted to the AI model, which enriches it with the result of the prediction.
The latter is transmitted to a transformation service whose role is to extract the train's action, transmit it to the train controller to slow down or stop the train and at the same time send the event to the streaming service (**Kafka**) deployed on a remote Openshift, which displays the images and the prediction in real time. The latter is transmitted to a transformation service whose role is to extract the train's action, transmit it to the train controller to slow down or stop the train and at the same time send the event to the streaming service (**Kafka**) deployed on a remote Openshift, which displays the images and the prediction in real time.
{{< attachedFigure src="mission-impossible-software-architecture.png" >}} ![](mission-impossible-software-architecture.png)
And finally, we had to build an artificial intelligence model. And finally, we had to build an artificial intelligence model.
To do this, we followed best practices for managing the model's lifecycle, known as **MLOps**: To do this, we followed best practices for managing the model's lifecycle, known as **MLOps**:
@ -137,9 +137,9 @@ To do this, we followed best practices for managing the model's lifecycle, known
- **Measure performance and re-train**: By observing the model's behaviour, we were able to measure the quality of the predictions and note that not all **Lego** panels were well recognised. - **Measure performance and re-train**: By observing the model's behaviour, we were able to measure the quality of the predictions and note that not all **Lego** panels were well recognised.
We decided to re-train the model by refining it with an enriched dataset. We decided to re-train the model by refining it with an enriched dataset.
{{< attachedFigure src="mission-impossible-ai.png" >}} ![](mission-impossible-ai.png)
If you weren't able to come and see us on the stand, you can catch up with us in the video below (captured during the {{< internalLink path="/speaking/platform-day-2024/index.md" >}}). If you weren't able to come and see us on the stand, you can catch up with us in the video below (captured during the [](/speaking/platform-day-2024/index.md)).
You can see the train stop when it detects the corresponding road sign. You can see the train stop when it detects the corresponding road sign.
{{< embeddedVideo src="mission-impossible-demo.mp4" autoplay="true" loop="true" muted="true" width="1920" height="1080" >}} {{< embeddedVideo src="mission-impossible-demo.mp4" autoplay="true" loop="true" muted="true" width="1920" height="1080" >}}

3
content/english/speaking/red-hat-tech-exchange-2018/index.md

@ -14,4 +14,5 @@ From the 17th to 21th Septembre 2018, I co-presented two sessions:
For the session _An API Journey: from mock to deployment_, we received an award For the session _An API Journey: from mock to deployment_, we received an award
based on the amazing feedbacks from the public! based on the amazing feedbacks from the public!
{{< attachedFigure src="2018-10-21-RHTE-Award.jpg" title="We received our award, on-stage!" >}} ![We received our award, on-stage!](2018-10-21-RHTE-Award.jpg "We received our award, on-stage!")

3
content/english/speaking/red-hat-tech-exchange-2019/index.md

@ -13,4 +13,5 @@ During the three Red Hat Tech Exchange sessions (Americas, EMEA and APAC), I pre
The session _Leverage the power of open source communities to manage your APIs_ has been the second best voted session in RHTE APAC! The session _Leverage the power of open source communities to manage your APIs_ has been the second best voted session in RHTE APAC!
{{< attachedFigure src="2019-10-21-RHTE-Award.png" title="I received an award!" >}} ![I received an award!](2019-10-21-RHTE-Award.png "I received an award!")

2
content/english/speaking/red-hat-tech-exchange-2023/index.md

@ -22,7 +22,7 @@ An application at the headquarter displays the parcels moving from one hub to an
The whole room had a lot of fun! The whole room had a lot of fun!
{{< attachedFigure src="Image_20230203_090806_445.jpeg" title="I presented the Lab organization." >}} ![I presented the Lab organization.](Image_20230203_090806_445.jpeg "I presented the Lab organization.")
If you want to play with the Lab, all the code is under the [RHTE-2023-Edge-Lab](https://github.com/RHTE-2023-Edge-Lab) organization. If you want to play with the Lab, all the code is under the [RHTE-2023-Edge-Lab](https://github.com/RHTE-2023-Edge-Lab) organization.

2
content/english/speaking/techweek-sg-2019/index.md

@ -8,7 +8,7 @@ draft: false
On the 12th November 2019, I spoke at Société Générale for their TechWeek event on a session named _"Accelerate and secure your API releases with Microcks"_. On the 12th November 2019, I spoke at Société Générale for their TechWeek event on a session named _"Accelerate and secure your API releases with Microcks"_.
{{< screenshotOf src="tweet-YadaYac.png" href="https://twitter.com/YadaYac/status/1194254972872986624" >}} [![Tweet by @YadaYac](tweet-YadaYac.png)](https://twitter.com/YadaYac/status/1194254972872986624)
The session was a one hour lab focusing on the design, mock and tests of REST APIs. The session was a one hour lab focusing on the design, mock and tests of REST APIs.
The [lab documents](https://microcks-workshop.netlify.com/) and [source code](https://github.com/nmasse-itix/microcks-workshop) are freely available! The [lab documents](https://microcks-workshop.netlify.com/) and [source code](https://github.com/nmasse-itix/microcks-workshop) are freely available!

1
content/english/topics/_index.md

@ -1,5 +1,6 @@
--- ---
title: "Expertise" title: "Expertise"
description: "The topics covered on this website: API Management, containers, security, performance testing, and more."
--- ---
During my various interventions on customer site, I had to work on various subjects for which I have written one or more articles. During my various interventions on customer site, I had to work on various subjects for which I have written one or more articles.

1
content/english/writing/_index.md

@ -1,5 +1,6 @@
--- ---
title: "Writing" title: "Writing"
description: "Articles written by Nicolas Massé as a guest author for other websites and for the specialized press."
--- ---
I post articles from time to time on various websites as a guest. I have gathered a list of my recent posts below. I post articles from time to time on various websites as a guest. I have gathered a list of my recent posts below.

2
content/english/writing/apis-as-a-product-get-started-in-no-time.md

@ -8,6 +8,6 @@ topics:
- API Management - API Management
--- ---
In the previous article, [APIs as a Product: Get the value out of your APIs]({{< relref "apis-as-a-product-get-the-value-out-of-your-apis.md" >}}), we presented a new approach called “APIs as a Product” to maximize the value of your APIs. In this article, we show how to quickly get started with APIs as a Product using the new features of Red Hat 3scale API Management 2.7. In the previous article, [APIs as a Product: Get the value out of your APIs](apis-as-a-product-get-the-value-out-of-your-apis.md), we presented a new approach called “APIs as a Product” to maximize the value of your APIs. In this article, we show how to quickly get started with APIs as a Product using the new features of Red Hat 3scale API Management 2.7.
[Continue reading](https://developers.redhat.com/blog/2019/12/03/apis-as-a-product-get-started-in-no-time/) [Continue reading](https://developers.redhat.com/blog/2019/12/03/apis-as-a-product-get-started-in-no-time/)

2
content/english/writing/how-size-your-projects-red-hats-single-sign-technology.md

@ -3,7 +3,7 @@ title: "How to size your projects for Red Hat's single sign-on technology"
date: 2021-06-07T00:00:00+02:00 date: 2021-06-07T00:00:00+02:00
draft: false draft: false
opensource: opensource:
- keycloak - Keycloak
- K6 - K6
topics: topics:
- Performance testing - Performance testing

2
content/english/writing/red-hat-single-sign-on-give-it-a-try-for-no-cost.md

@ -3,7 +3,7 @@ title: "Red Hat Single Sign-On: Give it a try for no cost!"
date: 2019-02-07T00:00:00+02:00 date: 2019-02-07T00:00:00+02:00
draft: false draft: false
opensource: opensource:
- keycloak - Keycloak
--- ---
In a software world where each day is more hostile than the previous one, security matters and developers are coping with more and more non-functional requirements about security. The most common ones are the “OWASP Top 10”: the ten security risks that every developer should know. There are many more security risks you should care about, but those ten risks are the ones having the most impact on the security of your software. Among them are authentication and access control. In a software world where each day is more hostile than the previous one, security matters and developers are coping with more and more non-functional requirements about security. The most common ones are the “OWASP Top 10”: the ten security risks that every developer should know. There are many more security risks you should care about, but those ten risks are the ones having the most impact on the security of your software. Among them are authentication and access control.

2
content/english/writing/using-a-public-certificate-with-red-hat-single-sign-on-keycloak.md

@ -3,7 +3,7 @@ title: "Using a public certificate with Red Hat Single Sign-On/Keycloak"
date: 2019-02-06T00:00:00+02:00 date: 2019-02-06T00:00:00+02:00
draft: false draft: false
opensource: opensource:
- keycloak - Keycloak
--- ---
When deploying Red Hat Single Sign-On/Keycloak for a test or a proof of concept, most users will choose to use a self-signed certificate as explained in the official documentation. When deploying Red Hat Single Sign-On/Keycloak for a test or a proof of concept, most users will choose to use a self-signed certificate as explained in the official documentation.

8
content/french/_index.md

@ -3,9 +3,9 @@ title: "Nicolas Massé"
--- ---
Je suis Nicolas Massé, vous me trouverez sur les réseaux sociaux sous le pseudo "nmasse-itix". Je suis Nicolas Massé, vous me trouverez sur les réseaux sociaux sous le pseudo "nmasse-itix".
Développeur de formation, mon métier actuel est consultant avant-vente dans les domaines de l'[API Management]({{<relref "/topics/api-management" >}}), le [Single Sign On]({{<relref "/topics/openid-connect" >}}) et les [conteneurs]({{<relref "/topics/containers" >}}). Développeur de formation, mon métier actuel est consultant avant-vente dans les domaines de l'[API Management](/topics/api-management), le [Single Sign On](/topics/openid-connect) et les [conteneurs](/topics/containers).
Je travaille principalement avec des technologies [Open Source]({{<relref "/opensource" >}}) telles que [Keycloak]({{<relref "/opensource/keycloak" >}}), [3scale]({{<relref "/opensource/3scale" >}}) ou [OpenShift]({{<relref "/opensource/openshift" >}}). Je travaille principalement avec des technologies [Open Source](/opensource) telles que [Keycloak](/opensource/keycloak), [3scale](/opensource/3scale) ou [OpenShift](/opensource/openshift).
Je partage mon [expertise]({{<relref "/topics" >}}) et mes centres d'intérêt sur ce site, [ailleurs sur le web]({{<relref "/writing" >}}) et je donne occasionnellement des [conférences]({{<relref "/speaking" >}}). Je partage mon [expertise](/topics) et mes centres d'intérêt sur ce site, [ailleurs sur le web](/writing) et je donne occasionnellement des [conférences](/speaking).
Je vous invite à lire [mon blog]({{<relref "/blog" >}}) ou à en savoir plus [sur moi](https://www.linkedin.com/in/nicolasmasse/). Je vous invite à lire [mon blog](/blog) ou à en savoir plus [sur moi](https://www.linkedin.com/in/nicolasmasse/).

1
content/french/blog/_index.md

@ -1,3 +1,4 @@
--- ---
title: "Articles" title: "Articles"
description: "Articles techniques sur Kubernetes, OpenShift, l'API Management, le Single Sign-On, les conteneurs et le homelab, par Nicolas Massé."
--- ---

28
content/french/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md

@ -16,7 +16,7 @@ resources:
- '*.gif' - '*.gif'
--- ---
Lors du {{< internalLink path="/speaking/red-hat-summit-connect-france-2024/index.md" >}}, j'ai animé un atelier pour les développeurs intitulé "**Open Code Quest**". Lors du [](/speaking/red-hat-summit-connect-france-2024/index.md), j'ai animé un atelier pour les développeurs intitulé "**Open Code Quest**".
Dans cet atelier, les développeurs devaient coder des micro-services en utilisant Quarkus, OpenShift et un service d'Intelligence Artificielle : le modèle Granite d'IBM. Dans cet atelier, les développeurs devaient coder des micro-services en utilisant Quarkus, OpenShift et un service d'Intelligence Artificielle : le modèle Granite d'IBM.
L'atelier était conçu sous la forme d'une compétition de vitesse : les premiers à valider les trois exercices ont reçu une récompense. L'atelier était conçu sous la forme d'une compétition de vitesse : les premiers à valider les trois exercices ont reçu une récompense.
@ -75,7 +75,7 @@ Et c'est justement ce qui nous intéresse :
Et si les trois conditions précédentes sont vraies, on peut en déduire que l'utilisateur a terminé et validé l'exercice **hero**. Et si les trois conditions précédentes sont vraies, on peut en déduire que l'utilisateur a terminé et validé l'exercice **hero**.
Au cours du temps, ces *time series* progressent telles que représentées sur la figure suivante. Au cours du temps, ces *time series* progressent telles que représentées sur la figure suivante.
{{< attachedFigure src="exercise-validation.png" title="Lorsque les trois conditions sont réunies, l'exercice est validé." >}} ![Lorsque les trois conditions sont réunies, l'exercice est validé.](exercise-validation.png "Lorsque les trois conditions sont réunies, l'exercice est validé.")
C'est un bon début, non ? C'est un bon début, non ?
Si on fait la même chose pour les trois exercices, on peut savoir qui a terminé l'atelier dans son ensemble. Si on fait la même chose pour les trois exercices, on peut savoir qui a terminé l'atelier dans son ensemble.
@ -84,7 +84,7 @@ Vu que certains exercices prennent plus de temps que d'autres, on peut imaginer
C'est ce que j'ai essayé de modéliser dans la figure ci-dessous avec un poids de 55 pour le premier exercice, 30 pour le second et 45 pour le dernier. C'est ce que j'ai essayé de modéliser dans la figure ci-dessous avec un poids de 55 pour le premier exercice, 30 pour le second et 45 pour le dernier.
L'idée étant d'approcher une progression linéaire des points au cours du temps (1 point par minute). L'idée étant d'approcher une progression linéaire des points au cours du temps (1 point par minute).
{{< attachedFigure src="counting-scheme-no-time.png" title="Progression du nombre de points pour un utilisateur normal, lent et rapide au cours du temps et avec pondération de chaque exercise en fonction de la durée nominale de l'exercise." >}} ![Progression du nombre de points pour un utilisateur normal, lent et rapide au cours du temps et avec pondération de chaque exercise en fonction de la durée nominale de l'exercise.](counting-scheme-no-time.png "Progression du nombre de points pour un utilisateur normal, lent et rapide au cours du temps et avec pondération de chaque exercise en fonction de la durée nominale de l'exercise.")
Ça commence à prendre forme. Ça commence à prendre forme.
Mais si on regarde bien, à la fin de l'atelier (à la 150ème minute), tous les participants ont terminé et ont le même score. Mais si on regarde bien, à la fin de l'atelier (à la 150ème minute), tous les participants ont terminé et ont le même score.
@ -108,12 +108,12 @@ Est-ce qu'on ne pourrait pas activer un accélérateur à chaque validation d'un
Voilà qui rendrait la compétition plus engageante et plus amusante ! Voilà qui rendrait la compétition plus engageante et plus amusante !
Et c'est ce que j'ai essayé de modéliser sur le schéma ci-dessous. Et c'est ce que j'ai essayé de modéliser sur le schéma ci-dessous.
{{< attachedFigure src="counting-scheme-with-time.png" title="Progression du nombre de points pour un utilisateur normal, lent et rapide au cours du temps et avec accélérateur et pondération de chaque exercise en fonction du temps que met l'utilisateur à réaliser l'exercice." >}} ![Progression du nombre de points pour un utilisateur normal, lent et rapide au cours du temps et avec accélérateur et pondération de chaque exercise en fonction du temps que met l'utilisateur à réaliser l'exercice.](counting-scheme-with-time.png "Progression du nombre de points pour un utilisateur normal, lent et rapide au cours du temps et avec accélérateur et pondération de chaque exercise en fonction du temps que met l'utilisateur à réaliser l'exercice.")
Maintenant, la question est : est-ce qu'un utilisateur qui prend la tête dans le premier exercice acquiert un avantage significatif qui rendrait la compétition déséquilibrée ? Maintenant, la question est : est-ce qu'un utilisateur qui prend la tête dans le premier exercice acquiert un avantage significatif qui rendrait la compétition déséquilibrée ?
La réponse, nous l'avons obtenue lors des différentes répétitions qui ont eu lieu chez Red Hat avant le Jour J. La réponse, nous l'avons obtenue lors des différentes répétitions qui ont eu lieu chez Red Hat avant le Jour J.
{{< attachedFigure src="counting-scheme-dry-run.png" title="Validation du modèle de comptage des points lors d'un dry-run." >}} ![Validation du modèle de comptage des points lors d'un dry-run.](counting-scheme-dry-run.png "Validation du modèle de comptage des points lors d'un dry-run.")
Dans la capture d'écran ci-dessus, on voit que Batman a terminé l'exercice "hero" **tardivement**. Dans la capture d'écran ci-dessus, on voit que Batman a terminé l'exercice "hero" **tardivement**.
Mais en terminant l'exercice "villain" **très rapidement**, il a pu reprendre la tếte... **temporairement**. Mais en terminant l'exercice "villain" **très rapidement**, il a pu reprendre la tếte... **temporairement**.
@ -169,7 +169,7 @@ Des données doivent normalement apparaître dans le tableau de bord Grafana.
Pour en profiter pleinement, arrêtez le script `run.sh` avec un appui sur **Ctrl + C** et relancez le ! Pour en profiter pleinement, arrêtez le script `run.sh` avec un appui sur **Ctrl + C** et relancez le !
Au bout de quelques secondes, vous devriez voir apparaitre sur le tableau de bord des données toutes fraiches, comme dans la vidéo ci-dessous. Au bout de quelques secondes, vous devriez voir apparaitre sur le tableau de bord des données toutes fraiches, comme dans la vidéo ci-dessous.
{{< attachedFigure src="leaderboard-simulation.gif" title="Simulation de l'atelier Open Code Quest sur le banc d'essai afin de valider le système de comptage de points (vidéo accélérée 10x)." >}} ![Simulation de l'atelier Open Code Quest sur le banc d'essai afin de valider le système de comptage de points (vidéo accélérée 10x).](leaderboard-simulation.gif "Simulation de l'atelier Open Code Quest sur le banc d'essai afin de valider le système de comptage de points (vidéo accélérée 10x).")
### Requêtes Prometheus ### Requêtes Prometheus
@ -227,7 +227,7 @@ EOF
Copiez-collez la requète dans la section **Explore** de Grafana et vous devriez obtenir le graphe suivant. Copiez-collez la requète dans la section **Explore** de Grafana et vous devriez obtenir le graphe suivant.
{{< attachedFigure src="grafana-explore-opencodequest-leaderboard-hero.png" title="La métrique \"opencodequest_leaderboard_hero:prod\" représente l'état de complétude de l'exercice \"hero\" dans l'environnement \"prod\"." >}} ![La métrique "opencodequestleaderboardhero:prod" représente l'état de complétude de l'exercice "hero" dans l'environnement "prod".](grafana-explore-opencodequest-leaderboard-hero.png "La métrique \"opencodequest_leaderboard_hero:prod\" représente l'état de complétude de l'exercice \"hero\" dans l'environnement \"prod\".")
Il faut le lire de la manière suivante (note : 1728646377 = 13:32:57) : Il faut le lire de la manière suivante (note : 1728646377 = 13:32:57) :
@ -290,7 +290,7 @@ Ces trois requêtes sont conçues sur le même modèle.
Pour bien comprendre comment fonctionne cette requête, je vous propose de la scinder en deux : la partie `increase(...)` d'un coté et le reste de l'autre. Pour bien comprendre comment fonctionne cette requête, je vous propose de la scinder en deux : la partie `increase(...)` d'un coté et le reste de l'autre.
On superpose tout ça avec la requête précédente et ça donne la figure suivante. On superpose tout ça avec la requête précédente et ça donne la figure suivante.
{{< attachedFigure src="grafana-opencodequest-leaderboard-onetime-bonus.png" title="La métrique \"opencodequest_leaderboard_hero_onetime_bonus:prod\" représente le bonus temps alloué à un utilisateur lorsqu'il termine l'exercice \"hero\" dans l'environnement \"prod\"." >}} ![La métrique "opencodequestleaderboardheroonetimebonus:prod" représente le bonus temps alloué à un utilisateur lorsqu'il termine l'exercice "hero" dans l'environnement "prod".](grafana-opencodequest-leaderboard-onetime-bonus.png "La métrique \"opencodequest_leaderboard_hero_onetime_bonus:prod\" représente le bonus temps alloué à un utilisateur lorsqu'il termine l'exercice \"hero\" dans l'environnement \"prod\".")
De haut en bas, on peut observer : De haut en bas, on peut observer :
@ -320,7 +320,7 @@ On peut le voir comme l'intégrale de la *time serie*.
La figure suivante présente le fonctionnement de cette requête de manière plus parlante. La figure suivante présente le fonctionnement de cette requête de manière plus parlante.
{{< attachedFigure src="grafana-opencodequest-leaderboard-lifetime-bonus.png" title="La métrique \"opencodequest_leaderboard_hero_lifetime_bonus:prod\" représente le report à nouveau du bonus temps alloué à un utilisateur lorsqu'il termine l'exercice \"hero\" dans l'environnement \"prod\"." >}} ![La métrique "opencodequestleaderboardherolifetimebonus:prod" représente le report à nouveau du bonus temps alloué à un utilisateur lorsqu'il termine l'exercice "hero" dans l'environnement "prod".](grafana-opencodequest-leaderboard-lifetime-bonus.png "La métrique \"opencodequest_leaderboard_hero_lifetime_bonus:prod\" représente le report à nouveau du bonus temps alloué à un utilisateur lorsqu'il termine l'exercice \"hero\" dans l'environnement \"prod\".")
De haut en bas, on peut observer : De haut en bas, on peut observer :
@ -366,7 +366,7 @@ En effet, les 3 accélérateurs s'additionnent déjà, ce qui fait que l'utilisa
La figure suivante présente les 6 composantes de requête Prometheus permettant de calculer les points de l'utilisateur. La figure suivante présente les 6 composantes de requête Prometheus permettant de calculer les points de l'utilisateur.
{{< attachedFigure src="grafana-opencodequest-leaderboard.png" title="Les 6 composantes de la requête Prometheus calculant les scores des utilisateurs et le résultat." >}} ![Les 6 composantes de la requête Prometheus calculant les scores des utilisateurs et le résultat.](grafana-opencodequest-leaderboard.png "Les 6 composantes de la requête Prometheus calculant les scores des utilisateurs et le résultat.")
### *Recording Rules* ### *Recording Rules*
@ -440,7 +440,7 @@ La syntaxe `${user:regex}` permet à Grafana de remplacer `user=~"${user:regex}"
Pour montrer le classement instantané, j'ai utilisé la visualisation **Bar Chart** avec une transformation de type **Sort by** sur le champ **Value**. Pour montrer le classement instantané, j'ai utilisé la visualisation **Bar Chart** avec une transformation de type **Sort by** sur le champ **Value**.
{{< attachedFigure src="grafana-opencodequest-leaderboard-instant-snapshot.png" title="Paramètres de la visualisation Grafana pour le classement instantané." >}} ![Paramètres de la visualisation Grafana pour le classement instantané.](grafana-opencodequest-leaderboard-instant-snapshot.png "Paramètres de la visualisation Grafana pour le classement instantané.")
Les paramètres importants de cette visualisation sont : Les paramètres importants de cette visualisation sont :
@ -452,7 +452,7 @@ Les paramètres importants de cette visualisation sont :
Pour suivre la progression des points au cours du temps, j'ai opté pour la visualisation **Time series**. Pour suivre la progression des points au cours du temps, j'ai opté pour la visualisation **Time series**.
{{< attachedFigure src="grafana-opencodequest-leaderboard-points-over-time.png" title="Paramètres de la visualisation Grafana pour la progression des points." >}} ![Paramètres de la visualisation Grafana pour la progression des points.](grafana-opencodequest-leaderboard-points-over-time.png "Paramètres de la visualisation Grafana pour la progression des points.")
Les paramètres importants de cette visualisation sont : Les paramètres importants de cette visualisation sont :
@ -478,7 +478,7 @@ Quant à la question qui est sur toutes les lèvres : est-ce qu'il y a eu de la
La réponse est un grand **OUI** ! La réponse est un grand **OUI** !
Et il y a eu du frisson lors de l'annonce des résultats... Et il y a eu du frisson lors de l'annonce des résultats...
{{< attachedFigure src="grafana-opencodequest-points.png" title="Progression des points des 74 participants lors de l'Open Code Quest." >}} ![Progression des points des 74 participants lors de l'Open Code Quest.](grafana-opencodequest-points.png "Progression des points des 74 participants lors de l'Open Code Quest.")
Observez toutes ces courbes qui se croisent, tous ces super-héros en compétition pour la première place ! Observez toutes ces courbes qui se croisent, tous ces super-héros en compétition pour la première place !
@ -490,4 +490,4 @@ Ce projet a non seulement mis en lumière des technologies comme Quarkus, OpenSh
Concevoir le Leaderboard, bien que complexe, a ajouté une dimension compétitive motivante à l’atelier. Concevoir le Leaderboard, bien que complexe, a ajouté une dimension compétitive motivante à l’atelier.
Le jour J, voir les participants rivaliser de rapidité tout en explorant les solutions Red Hat a été incroyablement gratifiant. Le jour J, voir les participants rivaliser de rapidité tout en explorant les solutions Red Hat a été incroyablement gratifiant.
Et pour savoir comment j'ai implémenté ce Leaderboard dans une architecture multi-cluster avec Red Hat ACM, c'est par ici : {{< internalLink path="/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md" >}}. Et pour savoir comment j'ai implémenté ce Leaderboard dans une architecture multi-cluster avec Red Hat ACM, c'est par ici : [](/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md).

8
content/french/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md

@ -17,7 +17,7 @@ resources:
- '*.gif' - '*.gif'
--- ---
Après avoir révélé les coulisses de la conception du Leaderboard pour l'atelier "Open Code Quest" lors du {{< internalLink path="/speaking/red-hat-summit-connect-france-2024/index.md" >}}, il est temps de plonger plus en détail dans son implémentation pratique ! Après avoir révélé les coulisses de la conception du Leaderboard pour l'atelier "Open Code Quest" lors du [](/speaking/red-hat-summit-connect-france-2024/index.md), il est temps de plonger plus en détail dans son implémentation pratique !
Dans cet article, je vais vous guider à travers la configuration de **Red Hat Advanced Cluster Management** ainsi que les différentes adapatations nécessaires pour connecter le *Leaderboard* créé précédemment avec l'infrastructure de l'**Open Code Quest**. Dans cet article, je vais vous guider à travers la configuration de **Red Hat Advanced Cluster Management** ainsi que les différentes adapatations nécessaires pour connecter le *Leaderboard* créé précédemment avec l'infrastructure de l'**Open Code Quest**.
@ -25,7 +25,7 @@ Embarquez avec moi pour cette nouvelle étape, plus technique que la précédent
<!--more--> <!--more-->
Cet article est la suite de {{< internalLink path="/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md" >}}. Cet article est la suite de [](/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md).
Si vous ne l'avez pas lu, je vous conseille de le lire avant pour mieux comprendre le contexte. Si vous ne l'avez pas lu, je vous conseille de le lire avant pour mieux comprendre le contexte.
## Requêtes Prometheus ## Requêtes Prometheus
@ -227,7 +227,7 @@ L'observabilité est un module supplémentaire (dans le sens où il n'est pas in
Le schéma suivant présente l'architecture du module d'observabilité dans **Red Hat Advanced Cluster Management**. Le schéma suivant présente l'architecture du module d'observabilité dans **Red Hat Advanced Cluster Management**.
Je l'ai créé en observant les relations entre les composants à partir d'une installation d'ACM en version 2.11. Je l'ai créé en observant les relations entre les composants à partir d'une installation d'ACM en version 2.11.
{{< attachedFigure src="redhat-acm-observability-architecture.svg" title="Architecture logique de l'observabilité dans Red Hat Advanced Cluster Management 2.11" >}} ![Architecture logique de l'observabilité dans Red Hat Advanced Cluster Management 2.11](redhat-acm-observability-architecture.svg "Architecture logique de l'observabilité dans Red Hat Advanced Cluster Management 2.11")
Les composants déployés sur le cluster central sont en **vert**, ceux déployés sur les clusters managés sont en **bleu** et les éléments de configuration en **gris**. Les composants déployés sur le cluster central sont en **vert**, ceux déployés sur les clusters managés sont en **bleu** et les éléments de configuration en **gris**.
J'ai aussi illustré les deux endroits possibles pour le calcul des *recording rules*, en **jaune**. J'ai aussi illustré les deux endroits possibles pour le calcul des *recording rules*, en **jaune**.
@ -430,7 +430,7 @@ read -q "?press any key to continue "
./switch-to-grafana-admin.sh "$(oc whoami)" ./switch-to-grafana-admin.sh "$(oc whoami)"
``` ```
Puis, créer le tableau de bord "Red Hat Summit Connect 2024", comme expliqué dans l'article {{< internalLink path="/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md" >}}. Puis, créer le tableau de bord "Red Hat Summit Connect 2024", comme expliqué dans l'article [](/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md).
Et enfin, exporter le tableau de bord sous la forme d'une ConfigMap. Et enfin, exporter le tableau de bord sous la forme d'une ConfigMap.

34
content/french/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-aws/index.md

@ -15,7 +15,7 @@ resources:
- '*.svg' - '*.svg'
--- ---
En 2022, j'ai écrit un article sur ce sujet intitulé {{< internalLink path="/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-and-qemu/index.md" >}}. En 2022, j'ai écrit un article sur ce sujet intitulé [](/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-and-qemu/index.md).
L'article relatait la configuration que j'avais mise en place pour mes projets personnels. L'article relatait la configuration que j'avais mise en place pour mes projets personnels.
Il a même un peu débordé de sa vocation initiale puisqu'il a également été utilisé par plusieurs collègues chez Red Hat qui avaient le même besoin. Il a même un peu débordé de sa vocation initiale puisqu'il a également été utilisé par plusieurs collègues chez Red Hat qui avaient le même besoin.
Si la configuration décrite dans ce précédent article est toujours d'actualité, l'approche est un peu datée. Si la configuration décrite dans ce précédent article est toujours d'actualité, l'approche est un peu datée.
@ -56,7 +56,7 @@ Si le précédent article ciblait les plateformes Kubernetes *vanilla*, dans cel
Dans les pipelines Tekton, j'utilise **Buildah** pour construire et pousser sur la *registry* les images de conteneurs. Dans les pipelines Tekton, j'utilise **Buildah** pour construire et pousser sur la *registry* les images de conteneurs.
{{< attachedFigure src="overview.svg" title="Vue d'ensemble de la configuration mise en oeuvre." >}} ![Vue d'ensemble de la configuration mise en oeuvre.](overview.svg "Vue d'ensemble de la configuration mise en oeuvre.")
## Déploiement d'OpenShift sur AWS en mode multi-architecture ## Déploiement d'OpenShift sur AWS en mode multi-architecture
@ -105,7 +105,7 @@ curl -sfL https://mirror.openshift.com/pub/openshift-v4/multi/clients/ocp/4.15.1
Créer le fichier **install-config.yaml**. Créer le fichier **install-config.yaml**.
Les zones surlignées sont les points importants du fichier. Les zones surlignées sont les points importants du fichier.
{{< highlightFile "install-config.yaml" "yaml" "hl_lines=10 15 26 29" >}} ```yaml {filename="install-config.yaml" hl_lines="10 15 26 29"}
additionalTrustBundlePolicy: Proxyonly additionalTrustBundlePolicy: Proxyonly
apiVersion: v1 apiVersion: v1
baseDomain: aws.itix.cloud baseDomain: aws.itix.cloud
@ -154,7 +154,7 @@ publish: External
pullSecret: 'REDACTED' pullSecret: 'REDACTED'
sshKey: | sshKey: |
REDACTED REDACTED
{{< /highlightFile >}} ```
J'ai choisis de laisser la déclaration des *Compute nodes* mais avec un *replicas* à 0 car cela simplifie l'instanciation du *Compute node* **x86_64** par la suite. J'ai choisis de laisser la déclaration des *Compute nodes* mais avec un *replicas* à 0 car cela simplifie l'instanciation du *Compute node* **x86_64** par la suite.
@ -169,7 +169,7 @@ Comme la [documentation OpenShift 4.15](https://docs.openshift.com/container-pla
Editer le fichier **manifests/cvo-overrides.yaml** et ajouter la section **additionalEnabledCapabilities**. Editer le fichier **manifests/cvo-overrides.yaml** et ajouter la section **additionalEnabledCapabilities**.
Ne faites pas un copier-coller du fichier complet ci-dessous car le **clusterID** est spécifique à votre installation ! Ne faites pas un copier-coller du fichier complet ci-dessous car le **clusterID** est spécifique à votre installation !
{{< highlightFile "manifests/cvo-overrides.yaml" "yaml" "hl_lines=10-21" >}} ```yaml {filename="manifests/cvo-overrides.yaml" hl_lines="10-21"}
apiVersion: config.openshift.io/v1 apiVersion: config.openshift.io/v1
kind: ClusterVersion kind: ClusterVersion
metadata: metadata:
@ -191,7 +191,7 @@ spec:
- OperatorLifecycleManager - OperatorLifecycleManager
- Build - Build
- DeploymentConfig - DeploymentConfig
{{< /highlightFile >}} ```
Démarrer l'installation du cluster sur AWS. Démarrer l'installation du cluster sur AWS.
@ -362,7 +362,7 @@ oc -n openshift-machine-api scale machineset build-multiarch-tw9w9-aarch64-worke
- 1 *Compute node* **x86_64** - 1 *Compute node* **x86_64**
- 1 *Compute node* **arm64** - 1 *Compute node* **arm64**
{{< attachedFigure src="openshift-console-three-nodes-x86_64-and-arm64.png" title="Console OpenShift affichant trois noeuds : deux noeuds x86_64 et un arm64." >}} ![Console OpenShift affichant trois noeuds : deux noeuds x8664 et un arm64.](openshift-console-three-nodes-x86_64-and-arm64.png "Console OpenShift affichant trois noeuds : deux noeuds x86_64 et un arm64.")
## Installation de l'opérateur Tekton ## Installation de l'opérateur Tekton
@ -453,7 +453,7 @@ spec:
Créer un volume EFS en suivant les étapes décrites dans la [documentation AWS](https://docs.aws.amazon.com/efs/latest/ug/gs-step-two-create-efs-resources.html). Créer un volume EFS en suivant les étapes décrites dans la [documentation AWS](https://docs.aws.amazon.com/efs/latest/ug/gs-step-two-create-efs-resources.html).
{{< attachedFigure src="aws-console-efs.png" title="Console AWS montrant un volume EFS." >}} ![Console AWS montrant un volume EFS.](aws-console-efs.png "Console AWS montrant un volume EFS.")
Créer la **StorageClass** associée au volume EFS. Créer la **StorageClass** associée au volume EFS.
Le champ **fileSystemId** correspond à la deuxième colonne de la capture d'écran ci-dessus. Le champ **fileSystemId** correspond à la deuxième colonne de la capture d'écran ci-dessus.
@ -476,7 +476,7 @@ parameters:
En suivant la documentation [OpenShift 4.15](https://docs.openshift.com/container-platform/4.15/storage/container_storage_interface/persistent-storage-csi-aws-efs.html#efs-create-volume_persistent-storage-csi-aws-efs), modifier le **Security Group** EFS pour autoriser les noeuds OpenShift à accéder au stockage via le protocole NFS. En suivant la documentation [OpenShift 4.15](https://docs.openshift.com/container-platform/4.15/storage/container_storage_interface/persistent-storage-csi-aws-efs.html#efs-create-volume_persistent-storage-csi-aws-efs), modifier le **Security Group** EFS pour autoriser les noeuds OpenShift à accéder au stockage via le protocole NFS.
{{< attachedFigure src="aws-console-security-group.png" title="Console AWS montrant la configuration du Security Group EFS." >}} ![Console AWS montrant la configuration du Security Group EFS.](aws-console-security-group.png "Console AWS montrant la configuration du Security Group EFS.")
À ce stade, le service AWS EFS est accessible depuis le cluster OpenShift mais il n'est pas encore utilisable par les pipelines Tekton. À ce stade, le service AWS EFS est accessible depuis le cluster OpenShift mais il n'est pas encore utilisable par les pipelines Tekton.
En effet, l'**affinity-assistant** de Tekton interdit à un volume persistant RWX d'être monté à deux endroits en même temps (erreur *"more than one PersistentVolumeClaim is bound"*). En effet, l'**affinity-assistant** de Tekton interdit à un volume persistant RWX d'être monté à deux endroits en même temps (erreur *"more than one PersistentVolumeClaim is bound"*).
@ -533,7 +533,7 @@ Les spécificités de cette tâche sont liées au pré-requis nécessaire à l'e
- La variable d'environnement `STORAGE_DRIVER=vfs` désactive l'utilisation de **overlayfs**. - La variable d'environnement `STORAGE_DRIVER=vfs` désactive l'utilisation de **overlayfs**.
- Enfin, la *capability* **SETFCAP** est affectée à **buildah**. - Enfin, la *capability* **SETFCAP** est affectée à **buildah**.
{{< highlightFile "tekton/task-buildah-build.yaml" "yaml" "hl_lines=19-20 25-26 33-35 37-38" >}} ```yaml {filename="tekton/task-buildah-build.yaml" hl_lines="19-20 25-26 33-35 37-38"}
apiVersion: tekton.dev/v1beta1 apiVersion: tekton.dev/v1beta1
kind: Task kind: Task
metadata: metadata:
@ -572,7 +572,7 @@ spec:
volumeMounts: volumeMounts:
- name: container-storage - name: container-storage
mountPath: /var/lib/containers mountPath: /var/lib/containers
{{< /highlightFile >}} ```
Le script exécuté par cette tâche est concis (seulement deux étapes) : Le script exécuté par cette tâche est concis (seulement deux étapes) :
@ -638,11 +638,11 @@ Le premier cas d'usage présenté au début de cet article porte sur l'utilisati
Le pipeline correspondant récupère le code source, exécute un **buildah build** pour chaque architecture (**x86_64** et **arm64**) et pousse le manifeste sur la *registry*. Le pipeline correspondant récupère le code source, exécute un **buildah build** pour chaque architecture (**x86_64** et **arm64**) et pousse le manifeste sur la *registry*.
{{< attachedFigure src="tekton-pipeline-containerfile.png" title="Console OpenShift montrant l'exécution du pipeline Tekton multi-architecture (Containerfile construisant une image de conteneur de l'utilitaire `htop`)." >}} ![Console OpenShift montrant l'exécution du pipeline Tekton multi-architecture (Containerfile construisant une image de conteneur de l'utilitaire htop).](tekton-pipeline-containerfile.png "Console OpenShift montrant l'exécution du pipeline Tekton multi-architecture (Containerfile construisant une image de conteneur de l'utilitaire `htop`).")
L'image résultante sur la registry quay.io montre bien un manifeste contenant deux images : une pour **x86_64** et une pour **arm64**. L'image résultante sur la registry quay.io montre bien un manifeste contenant deux images : une pour **x86_64** et une pour **arm64**.
{{< attachedFigure src="quay-image-containerfile.png" title="Console Quay montrant une image multi-architecture de l'utilitaire `htop`)." >}} ![Console Quay montrant une image multi-architecture de l'utilitaire htop).](quay-image-containerfile.png "Console Quay montrant une image multi-architecture de l'utilitaire `htop`).")
### Pipeline "NodeJS" ### Pipeline "NodeJS"
@ -651,14 +651,14 @@ Le pipeline NodeJS est sensiblement différent dans le sens où le dépôt Git e
La raison est que la commande **npm install** va télécharger les dépendances de l'application et certaines de ces dépendances peuvent embarquer du code natif (c'est le cas de certaines bibliothèques bas niveau). La raison est que la commande **npm install** va télécharger les dépendances de l'application et certaines de ces dépendances peuvent embarquer du code natif (c'est le cas de certaines bibliothèques bas niveau).
Il faut donc un *workspace* séparé pour chaque architecture. Il faut donc un *workspace* séparé pour chaque architecture.
{{< attachedFigure src="tekton-pipeline-nodejs.png" title="Console OpenShift montrant l'exécution du pipeline Tekton multi-architecture construisant une application NodeJS." >}} ![Console OpenShift montrant l'exécution du pipeline Tekton multi-architecture construisant une application NodeJS.](tekton-pipeline-nodejs.png "Console OpenShift montrant l'exécution du pipeline Tekton multi-architecture construisant une application NodeJS.")
### Pipeline "Quarkus" ### Pipeline "Quarkus"
Le pipeline Quarkus est encore différent du pipeline NodeJS dans le sens où l'application Quarkus est compilée en mode JVM et donc la compilation n'a lieu qu'une seule fois. Le pipeline Quarkus est encore différent du pipeline NodeJS dans le sens où l'application Quarkus est compilée en mode JVM et donc la compilation n'a lieu qu'une seule fois.
Le bytecode contenu dans les fichiers Jar pouvant tourner de manière indifférenciée sur n'importe quelle architecture, il n'y a qu'un workspace Git partagé entre toutes les architectures. Le bytecode contenu dans les fichiers Jar pouvant tourner de manière indifférenciée sur n'importe quelle architecture, il n'y a qu'un workspace Git partagé entre toutes les architectures.
{{< attachedFigure src="tekton-pipeline-quarkus.png" title="Console OpenShift montrant l'exécution du pipeline Tekton multi-architecture construisant une application Quarkus." >}} ![Console OpenShift montrant l'exécution du pipeline Tekton multi-architecture construisant une application Quarkus.](tekton-pipeline-quarkus.png "Console OpenShift montrant l'exécution du pipeline Tekton multi-architecture construisant une application Quarkus.")
### Lier une tâche à une architecture ### Lier une tâche à une architecture
@ -666,7 +666,7 @@ Jusqu'à présent l'article ne dit pas comment Tekton s'organise pour exécuter
Il n'y a pas de magie ! Tout se passe dans le **PipelineRun** ! Il n'y a pas de magie ! Tout se passe dans le **PipelineRun** !
{{< highlightFile "examples/htop/tekton/pipelinerun.yaml" "yaml" "hl_lines=41-49" >}} ```yaml {filename="examples/htop/tekton/pipelinerun.yaml" hl_lines="41-49"}
apiVersion: tekton.dev/v1 apiVersion: tekton.dev/v1
kind: PipelineRun kind: PipelineRun
metadata: metadata:
@ -716,7 +716,7 @@ spec:
podTemplate: podTemplate:
nodeSelector: nodeSelector:
beta.kubernetes.io/arch: arm64 beta.kubernetes.io/arch: arm64
{{< /highlightFile >}} ```
Le champ **taskRunSpecs** permet d'ajouter des contraintes d'exécution à une ou plusieurs tâches du pipeline. Le champ **taskRunSpecs** permet d'ajouter des contraintes d'exécution à une ou plusieurs tâches du pipeline.

12
content/french/blog/connect-podman-container-to-openvswitch/index.md

@ -64,10 +64,10 @@ ip -n podman-c1 link set ivs123 up
Pour tester facilement notre setup, je propose de créer une image de conteneur avec tout le nécessaire. Pour tester facilement notre setup, je propose de créer une image de conteneur avec tout le nécessaire.
Pour cela, créer un fichier **Containerfile** avec le contenu suivant: Pour cela, créer un fichier **Containerfile** avec le contenu suivant:
{{< highlightFile "Containerfile" "docker" "" >}} ```docker {filename="Containerfile"}
FROM quay.io/centos/centos:stream10 FROM quay.io/centos/centos:stream10
RUN dnf install -y iproute bind-utils net-tools iputils && dnf clean all RUN dnf install -y iproute bind-utils net-tools iputils && dnf clean all
{{< / highlightFile >}} ```
Construire l'image de conteneur avec : Construire l'image de conteneur avec :
@ -116,7 +116,7 @@ ovs-vsctl add-br ivs
Créer le réseau libvirt qui référence le bridge **Open vSwitch**. Créer le réseau libvirt qui référence le bridge **Open vSwitch**.
Pour cela, vous devrez créer le fichier de définition au format XML : Pour cela, vous devrez créer le fichier de définition au format XML :
{{< highlightFile "ovs-net.xml" "xml" "" >}} ```xml {filename="ovs-net.xml"}
<network> <network>
<name>ivs</name> <name>ivs</name>
<forward mode="bridge" /> <forward mode="bridge" />
@ -126,7 +126,7 @@ Pour cela, vous devrez créer le fichier de définition au format XML :
<portgroup name='default' default='true'> <portgroup name='default' default='true'>
</portgroup> </portgroup>
</network> </network>
{{< /highlightFile >}} ```
Puis créer le réseau libvirt depuis ce fichier de définition : Puis créer le réseau libvirt depuis ce fichier de définition :
@ -250,7 +250,7 @@ Pour éviter de coder dans le Quadlet tous les paramètres réseau, je me suis a
Le quadlet résultat est celui-ci : Le quadlet résultat est celui-ci :
{{< highlightFile "/etc/containers/systemd/test-openvswitch.container" "ini" "" >}} ```ini {filename="/etc/containers/systemd/test-openvswitch.container"}
[Unit] [Unit]
Description=Sample container to test the integration with Open vSwitch Description=Sample container to test the integration with Open vSwitch
Before=openvswitch.service Before=openvswitch.service
@ -282,7 +282,7 @@ ExecStopPost=/bin/sh -Eeuo pipefail -c 'if ip -n $NS -br addr show | grep -q "^$
[Install] [Install]
# Start by default on boot # Start by default on boot
WantedBy=multi-user.target default.target WantedBy=multi-user.target default.target
{{< /highlightFile >}} ```
L'adresse mac statique du conteneur peut se définir au niveau de l'interface **Open vSwitch**. L'adresse mac statique du conteneur peut se définir au niveau de l'interface **Open vSwitch**.
Dans la commande suivante, les guillemets sont échappés car ils font partie de la valeur attendue par **Open vSwitch**. Dans la commande suivante, les guillemets sont échappés car ils font partie de la valeur attendue par **Open vSwitch**.

26
content/french/blog/decouverte-de-coreos/index.md

@ -74,7 +74,7 @@ Vous remarquerez que le fichier [ignition](https://coreos.github.io/ignition/con
C'est également une spécificité de CoreOS : tous les logiciels qui tournent sur CoreOS doivent tourner sous la forme de conteneurs, instanciés et démarrés depuis un *unit* systemd ou provisionné par OpenShift. C'est également une spécificité de CoreOS : tous les logiciels qui tournent sur CoreOS doivent tourner sous la forme de conteneurs, instanciés et démarrés depuis un *unit* systemd ou provisionné par OpenShift.
Note: si vous souhaitez tout de même installer un package RPM, il est possible de personnaliser l'image CoreOS avec vos modification. Note: si vous souhaitez tout de même installer un package RPM, il est possible de personnaliser l'image CoreOS avec vos modification.
Je vous conseille alors la lecture de cet article : {{< internalLink path="/blog/build-your-own-distribution-on-fedora-coreos.md" >}}. Je vous conseille alors la lecture de cet article : [](/blog/build-your-own-distribution-on-fedora-coreos.md).
Autre différence majeure avec un système d'exploitation classique : les mises à jour s'effectuent de manière transactionnelle. Autre différence majeure avec un système d'exploitation classique : les mises à jour s'effectuent de manière transactionnelle.
Cela signifie qu'une mise à jour du système d'exploitation s'effectue dans son intégralité ou ne se fait pas du tout. Cela signifie qu'une mise à jour du système d'exploitation s'effectue dans son intégralité ou ne se fait pas du tout.
@ -133,7 +133,7 @@ Chaque version (identifiée par son empreinte SHA 256) contient l'ensemble du sy
La commande `ostree admin status` permet de lister les versions installées. La commande `ostree admin status` permet de lister les versions installées.
{{< highlight "sh" "hl_lines=2 14" >}} ```sh {hl_lines="2 14"}
[core@coreos ~]$ sudo ostree admin status [core@coreos ~]$ sudo ostree admin status
* rhcos eb6dd3b8b2912914f568af791a7ece826665cc78153a4e4d304acdaae1daacd1.0 * rhcos eb6dd3b8b2912914f568af791a7ece826665cc78153a4e4d304acdaae1daacd1.0
Version: 48.84.202106231817-0 Version: 48.84.202106231817-0
@ -150,7 +150,7 @@ boot etc lib media opt proc run srv sysroot usr
/ostree/deploy/rhcos/deploy/eb6dd3b8b2912914f568af791a7ece826665cc78153a4e4d304acdaae1daacd1.0/: /ostree/deploy/rhcos/deploy/eb6dd3b8b2912914f568af791a7ece826665cc78153a4e4d304acdaae1daacd1.0/:
bin dev home lib64 mnt ostree root sbin sys tmp var bin dev home lib64 mnt ostree root sbin sys tmp var
boot etc lib media opt proc run srv sysroot usr boot etc lib media opt proc run srv sysroot usr
{{< / highlight >}} ```
Pour éviter de gâcher de l'espace disque, les fichiers communs à deux versions sont partagés via un *hard link*. Pour éviter de gâcher de l'espace disque, les fichiers communs à deux versions sont partagés via un *hard link*.
Ici, la commande `ls` est identique entre les deux versions et ainsi les deux fichiers ont le même *inode*. Ici, la commande `ls` est identique entre les deux versions et ainsi les deux fichiers ont le même *inode*.
@ -191,7 +191,7 @@ Red Hat CoreOS est une version de Red Hat Enterprise Linux, packagée de manièr
Le premier indice se trouve dans le fichier /etc/os-release qui nous indique que la version de CoreOS embarquée avec OpenShift 4.8 est basée sur une Red Hat Enterprise Linux 8.4. Le premier indice se trouve dans le fichier /etc/os-release qui nous indique que la version de CoreOS embarquée avec OpenShift 4.8 est basée sur une Red Hat Enterprise Linux 8.4.
{{< highlightFile "/etc/os-release" "sh" "hl_lines=18" >}} ```sh {filename="/etc/os-release" hl_lines="18"}
NAME="Red Hat Enterprise Linux CoreOS" NAME="Red Hat Enterprise Linux CoreOS"
VERSION="48.84.202108062347-0" VERSION="48.84.202108062347-0"
ID="rhcos" ID="rhcos"
@ -211,32 +211,32 @@ REDHAT_SUPPORT_PRODUCT_VERSION="4.8"
OPENSHIFT_VERSION="4.8" OPENSHIFT_VERSION="4.8"
RHEL_VERSION="8.4" RHEL_VERSION="8.4"
OSTREE_VERSION='48.84.202108062347-0' OSTREE_VERSION='48.84.202108062347-0'
{{< / highlightFile >}} ```
La version du noyau Linux est, à peu de choses près, la même sur CoreOS que sur RHEL. La version du noyau Linux est, à peu de choses près, la même sur CoreOS que sur RHEL.
{{< highlightWithTitle "RHEL 8.4" "sh" "hl_lines=3" >}} ```sh {title="RHEL 8.4" hl_lines="3"}
[nmasse@localhost ~]$ rpm -qa |grep kernel [nmasse@localhost ~]$ rpm -qa |grep kernel
kernel-modules-4.18.0-305.el8.x86_64 kernel-modules-4.18.0-305.el8.x86_64
kernel-4.18.0-305.el8.x86_64 kernel-4.18.0-305.el8.x86_64
kernel-core-4.18.0-305.el8.x86_64 kernel-core-4.18.0-305.el8.x86_64
kernel-tools-4.18.0-305.el8.x86_64 kernel-tools-4.18.0-305.el8.x86_64
kernel-tools-libs-4.18.0-305.el8.x86_64 kernel-tools-libs-4.18.0-305.el8.x86_64
{{< / highlightWithTitle >}} ```
&nbsp; &nbsp;
{{< highlightWithTitle "CoreOS" "sh" "hl_lines=5" >}} ```sh {title="CoreOS" hl_lines="5"}
[core@coreos ~]$ rpm -qa |grep kernel [core@coreos ~]$ rpm -qa |grep kernel
kernel-core-4.18.0-305.10.2.el8_4.x86_64 kernel-core-4.18.0-305.10.2.el8_4.x86_64
kernel-modules-4.18.0-305.10.2.el8_4.x86_64 kernel-modules-4.18.0-305.10.2.el8_4.x86_64
kernel-modules-extra-4.18.0-305.10.2.el8_4.x86_64 kernel-modules-extra-4.18.0-305.10.2.el8_4.x86_64
kernel-4.18.0-305.10.2.el8_4.x86_64 kernel-4.18.0-305.10.2.el8_4.x86_64
{{< / highlightWithTitle >}} ```
La glibc est exactement la même sur CoreOS et sur RHEL. La glibc est exactement la même sur CoreOS et sur RHEL.
{{< highlightWithTitle "RHEL 8.4" "sh" "hl_lines=4 7" >}} ```sh {title="RHEL 8.4" hl_lines="4 7"}
[nmasse@localhost ~]$ rpm -qa |grep glibc [nmasse@localhost ~]$ rpm -qa |grep glibc
glibc-langpack-en-2.28-151.el8.x86_64 glibc-langpack-en-2.28-151.el8.x86_64
glibc-common-2.28-151.el8.x86_64 glibc-common-2.28-151.el8.x86_64
@ -244,11 +244,11 @@ glibc-2.28-151.el8.x86_64
[nmasse@localhost ~]$ sha1sum /lib64/libc-2.28.so [nmasse@localhost ~]$ sha1sum /lib64/libc-2.28.so
5dd511ebea3476a03d710eff1bab8a72a47fdf71 /lib64/libc-2.28.so 5dd511ebea3476a03d710eff1bab8a72a47fdf71 /lib64/libc-2.28.so
{{< / highlightWithTitle >}} ```
&nbsp; &nbsp;
{{< highlightWithTitle "CoreOS" "sh" "hl_lines=4 7" >}} ```sh {title="CoreOS" hl_lines="4 7"}
[core@coreos ~]$ rpm -qa |grep glibc [core@coreos ~]$ rpm -qa |grep glibc
glibc-common-2.28-151.el8.x86_64 glibc-common-2.28-151.el8.x86_64
glibc-all-langpacks-2.28-151.el8.x86_64 glibc-all-langpacks-2.28-151.el8.x86_64
@ -256,7 +256,7 @@ glibc-2.28-151.el8.x86_64
[core@coreos ~]$ sha1sum /lib64/libc-2.28.so [core@coreos ~]$ sha1sum /lib64/libc-2.28.so
5dd511ebea3476a03d710eff1bab8a72a47fdf71 /lib64/libc-2.28.so 5dd511ebea3476a03d710eff1bab8a72a47fdf71 /lib64/libc-2.28.so
{{< / highlightWithTitle >}} ```
Et la [documentation OpenShift](https://docs.openshift.com/container-platform/4.9/architecture/architecture-rhcos.html) confirme ces investigations avec l'information suivante : Et la [documentation OpenShift](https://docs.openshift.com/container-platform/4.9/architecture/architecture-rhcos.html) confirme ces investigations avec l'information suivante :

48
content/french/blog/fibre-orange-remplacer-livebox-routeur-centos-stream-8/index.md

@ -51,12 +51,12 @@ Je l'ai déniché pour 660 € sur eBay avec les caractéristiques suivantes :
- 48 Go de DDR4 ECC - 48 Go de DDR4 ECC
- Carte réseau supplémentaire Intel I350 au format "FlexibleLOM", avec 4 ports RJ-45 - Carte réseau supplémentaire Intel I350 au format "FlexibleLOM", avec 4 ports RJ-45
{{< attachedFigure src="hp-dl20-gen9.png" title="Serveur HP DL20 Gen9, vue de face, vue de dos." >}} ![Serveur HP DL20 Gen9, vue de face, vue de dos.](hp-dl20-gen9.png "Serveur HP DL20 Gen9, vue de face, vue de dos.")
Pour me raccorder au réseau fibre optique d'Orange, j'ai conservé l'ONT (*Optical Network Termination*) qui m'a été fourni avec la Livebox. Pour me raccorder au réseau fibre optique d'Orange, j'ai conservé l'ONT (*Optical Network Termination*) qui m'a été fourni avec la Livebox.
D'un coté, je branche la jarretière optique, et de l'autre je branche le câble RJ-45 qui va jusqu'au serveur HP. D'un coté, je branche la jarretière optique, et de l'autre je branche le câble RJ-45 qui va jusqu'au serveur HP.
{{< attachedFigure src="Boitier-Fibre-Orange.jpeg" title="Boitier Fibre Orange. Source: [Wikipedia](https://commons.wikimedia.org/wiki/File:Boitier-Fibre-Orange_-_IMG_6456.jpg)" >}} ![Boitier Fibre Orange. Source: Wikipedia](Boitier-Fibre-Orange.jpeg "Boitier Fibre Orange. Source: [Wikipedia](https://commons.wikimedia.org/wiki/File:Boitier-Fibre-Orange_-_IMG_6456.jpg)")
## Logiciel ## Logiciel
@ -106,10 +106,10 @@ sudo dnf install dhcp-client-orange-isp
Configurer NetworkManager pour qu'il utilise dhclient plutôt que son client DHCP interne. Configurer NetworkManager pour qu'il utilise dhclient plutôt que son client DHCP interne.
{{< highlightFile "/etc/NetworkManager/conf.d/dhclient.conf" "ini" "hl_lines=2" >}} ```ini {filename="/etc/NetworkManager/conf.d/dhclient.conf" hl_lines="2"}
[main] [main]
dhcp=dhclient dhcp=dhclient
{{< / highlightFile >}} ```
Redémarrer NetworkManager. Redémarrer NetworkManager.
@ -127,7 +127,7 @@ Pour configurer le client DHCP pour l'authentification Orange, il vous faudra tr
Saisir le contenu du fichier /etc/dhcp/dhclient.conf. Saisir le contenu du fichier /etc/dhcp/dhclient.conf.
{{< highlightFile "/etc/dhcp/dhclient.conf" "c" "hl_lines=4 7-8" >}} ```c {filename="/etc/dhcp/dhclient.conf" hl_lines="4 7-8"}
option rfc3118-authentication code 90 = string; option rfc3118-authentication code 90 = string;
option dhcp-client-identifier code 61 = string; option dhcp-client-identifier code 61 = string;
@ -138,7 +138,7 @@ interface "eno2.832" {
send rfc3118-authentication 00:00:00:00:00:00:00:00:00:00:00:1a:09:00:00:05:58:01:03:41:01:0B:66:74:69:2F:64:75:6D:6D:79:3c:12:31:32:33:34:35:36:37:38:39:30:31:32:33:34:35:36:03:13:41:b9:80:f2:ea:3f:06:3b:2b:e7:08:ac:ec:9c:38:9e:ba; send rfc3118-authentication 00:00:00:00:00:00:00:00:00:00:00:1a:09:00:00:05:58:01:03:41:01:0B:66:74:69:2F:64:75:6D:6D:79:3c:12:31:32:33:34:35:36:37:38:39:30:31:32:33:34:35:36:03:13:41:b9:80:f2:ea:3f:06:3b:2b:e7:08:ac:ec:9c:38:9e:ba;
request subnet-mask,routers,domain-name,broadcast-address,dhcp-lease-time,dhcp-renewal-time,dhcp-rebinding-time,rfc3118-authentication; request subnet-mask,routers,domain-name,broadcast-address,dhcp-lease-time,dhcp-renewal-time,dhcp-rebinding-time,rfc3118-authentication;
} }
{{< / highlightFile >}} ```
## Configuration de l'interface réseau pour utiliser le VLAN 832 ## Configuration de l'interface réseau pour utiliser le VLAN 832
@ -163,12 +163,12 @@ Aussi, considérez la comme une indication plus qu'un tutoriel pas à pas.
Activer le routage des paquets IPv4 et IPv6. Activer le routage des paquets IPv4 et IPv6.
Ne pas oublier d'adapter le nom de l'interface réseau ! Ne pas oublier d'adapter le nom de l'interface réseau !
{{< highlightFile "/etc/sysctl.d/99-fibre-orange.conf" "ini" "hl_lines=3" >}} ```ini {filename="/etc/sysctl.d/99-fibre-orange.conf" hl_lines="3"}
net.ipv4.ip_forward=1 net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1
net/ipv6/conf/eno2.832/accept_ra=2 net/ipv6/conf/eno2.832/accept_ra=2
net.ipv4.conf.all.src_valid_mark=1 net.ipv4.conf.all.src_valid_mark=1
{{< / highlightFile >}} ```
Recharger les paramètres noyaux avec la commande **sysctl**. Recharger les paramètres noyaux avec la commande **sysctl**.
@ -185,7 +185,7 @@ Même mode opératoire que pour IPv4, il vous faudra trois choses :
Saisir le contenu du fichier /etc/dhcp/dhclient6.conf. Saisir le contenu du fichier /etc/dhcp/dhclient6.conf.
{{< highlightFile "/etc/dhcp/dhclient.conf" "c" "hl_lines=5 9-10" >}} ```c {filename="/etc/dhcp/dhclient.conf" hl_lines="5 9-10"}
option dhcp6.auth code 11 = string; option dhcp6.auth code 11 = string;
option dhcp6.vendorclass code 16 = string; option dhcp6.vendorclass code 16 = string;
option dhcp6.userclass code 15 = string; option dhcp6.userclass code 15 = string;
@ -198,13 +198,13 @@ interface "eno2.832" {
send dhcp6.auth 00:00:00:00:00:00:00:00:00:00:00:1a:09:00:00:05:58:01:03:41:01:0B:66:74:69:2F:64:75:6D:6D:79:3c:12:31:32:33:34:35:36:37:38:39:30:31:32:33:34:35:36:03:13:41:b9:80:f2:ea:3f:06:3b:2b:e7:08:ac:ec:9c:38:9e:ba; send dhcp6.auth 00:00:00:00:00:00:00:00:00:00:00:1a:09:00:00:05:58:01:03:41:01:0B:66:74:69:2F:64:75:6D:6D:79:3c:12:31:32:33:34:35:36:37:38:39:30:31:32:33:34:35:36:03:13:41:b9:80:f2:ea:3f:06:3b:2b:e7:08:ac:ec:9c:38:9e:ba;
also request dhcp6.name-servers, dhcp6.vendorclass, dhcp6.userclass, dhcp6.auth; also request dhcp6.name-servers, dhcp6.vendorclass, dhcp6.userclass, dhcp6.auth;
} }
{{< / highlightFile >}} ```
Créer le script de *dispatch* pour NetworkManager. Créer le script de *dispatch* pour NetworkManager.
Ce script sera appelé automatiquement par NetworkManager après un *up* ou un *down* de l'interface réseau et lancera le client DHCPv6 en mode *Prefix Delegation*. Ce script sera appelé automatiquement par NetworkManager après un *up* ou un *down* de l'interface réseau et lancera le client DHCPv6 en mode *Prefix Delegation*.
Ne pas oublier d'adapter le nom de l'interface réseau ! Ne pas oublier d'adapter le nom de l'interface réseau !
{{< highlightFile "/etc/NetworkManager/dispatcher.d/99-orange-ipv6" "sh" "hl_lines=5" >}} ```sh {filename="/etc/NetworkManager/dispatcher.d/99-orange-ipv6" hl_lines="5"}
#!/bin/bash #!/bin/bash
set -Eeuo pipefail set -Eeuo pipefail
@ -238,7 +238,7 @@ down)
esac esac
exit 0 exit 0
{{< / highlightFile >}} ```
Créer le répertoire **/etc/dhcp/dhclient-exit-hooks.d**. Créer le répertoire **/etc/dhcp/dhclient-exit-hooks.d**.
@ -250,7 +250,7 @@ Créer le script de *hook* pour dhclient.
Il sera appelé par le client DHCPv6 après obtention d'un préfixe IPv6 et a pour tâche d'affecter les adresses IPv6 aux différentes interfaces du serveur. Il sera appelé par le client DHCPv6 après obtention d'un préfixe IPv6 et a pour tâche d'affecter les adresses IPv6 aux différentes interfaces du serveur.
Ne pas oublier d'adapter le nom de l'interface réseau et le nom de vos interfaces réseaux internes (chez moi, elles s'appelle ivs1, ivs2, etc.) ! Ne pas oublier d'adapter le nom de l'interface réseau et le nom de vos interfaces réseaux internes (chez moi, elles s'appelle ivs1, ivs2, etc.) !
{{< highlightFile "/etc/dhcp/dhclient-exit-hooks.d/99-orange-ipv6" "sh" "hl_lines=5 12" >}} ```sh {filename="/etc/dhcp/dhclient-exit-hooks.d/99-orange-ipv6" hl_lines="5 12"}
#!/bin/bash #!/bin/bash
set -Eeuo pipefail set -Eeuo pipefail
@ -328,7 +328,7 @@ RELEASE)
esac esac
exit 0 exit 0
{{< / highlightFile >}} ```
N'oubliez pas de rendre ces deux scripts exécutables. N'oubliez pas de rendre ces deux scripts exécutables.
@ -351,7 +351,7 @@ sudo systemctl mask firewalld
Éditer le contenu du fichier **/etc/sysconfig/nftables.conf**. Éditer le contenu du fichier **/etc/sysconfig/nftables.conf**.
{{< highlightFile "/etc/sysconfig/nftables.conf" "ini" "hl_lines=" >}} ```ini {filename="/etc/sysconfig/nftables.conf"}
# Uncomment the include statement here to load the default config sample # Uncomment the include statement here to load the default config sample
# in /etc/nftables for nftables service. # in /etc/nftables for nftables service.
@ -360,11 +360,11 @@ include "/etc/nftables/itix.nft"
# To customize, either edit the samples in /etc/nftables, append further # To customize, either edit the samples in /etc/nftables, append further
# commands to the end of this file or overwrite it after first service # commands to the end of this file or overwrite it after first service
# start by calling: 'nft list ruleset >/etc/sysconfig/nftables.conf'. # start by calling: 'nft list ruleset >/etc/sysconfig/nftables.conf'.
{{< / highlightFile >}} ```
Créer le fichier **/etc/nftables/update.nft**. Créer le fichier **/etc/nftables/update.nft**.
{{< highlightFile "/etc/nftables/update.nft" "ini" "hl_lines=" >}} ```ini {filename="/etc/nftables/update.nft"}
#!/usr/sbin/nft -f #!/usr/sbin/nft -f
flush table inet itix-fw flush table inet itix-fw
@ -373,11 +373,11 @@ flush table ip itix-nat
delete table ip itix-nat delete table ip itix-nat
include "/etc/nftables/itix.nft" include "/etc/nftables/itix.nft"
{{< / highlightFile >}} ```
Créer le fichier **/etc/nftables/itix.nft**. Créer le fichier **/etc/nftables/itix.nft**.
{{< highlightFile "/etc/nftables/itix.nft" "c" "hl_lines=42 45 57 72 74 76 86" >}} ```c {filename="/etc/nftables/itix.nft" hl_lines="42 45 57 72 74 76 86"}
#!/usr/sbin/nft -f #!/usr/sbin/nft -f
table inet itix-fw { table inet itix-fw {
@ -467,7 +467,7 @@ table ip itix-nat {
} }
} }
{{< / highlightFile >}} ```
Activer et démarrer le service **nftables**. Activer et démarrer le service **nftables**.
@ -488,7 +488,7 @@ Rien n'est documenté officiellement, mais sur le forum [lafibre.info](https://l
Pour mettre en oeuvre ces règles, j'ai développé un script qui vérifie que les piles IPv4 et IPv6 sont opérationnelles et force un nouveau cycle DHCPv4 + DHCPv6 si nécessaire. Pour mettre en oeuvre ces règles, j'ai développé un script qui vérifie que les piles IPv4 et IPv6 sont opérationnelles et force un nouveau cycle DHCPv4 + DHCPv6 si nécessaire.
Pour éviter tout problème, je force également un renouvellement des baux DHCP toutes les 12 heures. Pour éviter tout problème, je force également un renouvellement des baux DHCP toutes les 12 heures.
{{< highlightFile "/usr/local/bin/fibre-orange" "sh" "hl_lines=5" >}} ```sh {filename="/usr/local/bin/fibre-orange" hl_lines="5"}
#!/bin/bash #!/bin/bash
set -Eeuo pipefail set -Eeuo pipefail
@ -613,7 +613,7 @@ help)
esac esac
exit 0 exit 0
{{< / highlightFile >}} ```
Rendre le script exécutable. Rendre le script exécutable.
@ -623,10 +623,10 @@ sudo chmod 755 /usr/local/bin/fibre-orange
Créer la crontab associée pour lancer ce script périodiquement. Créer la crontab associée pour lancer ce script périodiquement.
{{< highlightFile "/etc/cron.d/fibre-orange" "crontab" "hl_lines=" >}} ```crontab {filename="/etc/cron.d/fibre-orange"}
*/5 * * * * root /usr/local/bin/fibre-orange health-check */5 * * * * root /usr/local/bin/fibre-orange health-check
0 */12 * * * root /usr/local/bin/fibre-orange renew 0 */12 * * * root /usr/local/bin/fibre-orange renew
{{< / highlightFile >}} ```
## Conclusion ## Conclusion

28
content/french/blog/homelab-server-2u-short-depth-front-io-ampere-altra-arm64-architecture/index.md

@ -33,9 +33,9 @@ Ce serveur **HP DL20 Gen9** est équipé de :
- 2 disques 3,5" de 4 To chacun, configurés en RAID 1 par la carte RAID HP - 2 disques 3,5" de 4 To chacun, configurés en RAID 1 par la carte RAID HP
- 6 ports RJ-45 à 1 GbE - 6 ports RJ-45 à 1 GbE
{{< attachedFigure src="hp-dl20-gen9.webp" title="Le serveur HP DL20 Gen9 existant." >}} ![Le serveur HP DL20 Gen9 existant.](hp-dl20-gen9.webp "Le serveur HP DL20 Gen9 existant.")
Ce serveur est actuellement racké dans une baie informatique de 7U de haut, faible profondeur, au sous-sol de la maison et il fait actuellement tourner **CentOS Stream 8** que j'ai configuré en routeur (voir {{< internalLink path="/blog/fibre-orange-remplacer-livebox-routeur-centos-stream-8" >}}) et hyperviseur (avec **libvirt**). Ce serveur est actuellement racké dans une baie informatique de 7U de haut, faible profondeur, au sous-sol de la maison et il fait actuellement tourner **CentOS Stream 8** que j'ai configuré en routeur (voir [](/blog/fibre-orange-remplacer-livebox-routeur-centos-stream-8)) et hyperviseur (avec **libvirt**).
L'hyperviseur fait tourner plusieurs VM : L'hyperviseur fait tourner plusieurs VM :
@ -68,7 +68,7 @@ Les capacités d'expansion du **HP DL20 Gen9** sont bien limitées : 2 emplaceme
L'emplacement pleine hauteur est pris par le contrôleur RAID et l'emplacement demi hauteur est pris par la carte réseau 4 ports GbE. L'emplacement pleine hauteur est pris par le contrôleur RAID et l'emplacement demi hauteur est pris par la carte réseau 4 ports GbE.
Dernier point, et non des moindres, la carte mère et le CPU du **HP DL20 Gen9** ne supportent pas le **SR-IOV**. Ça m'oblige actuellement à faire tourner les fonctions de routage réseau directement sur l'hyperviseur. Dernier point, et non des moindres, la carte mère et le CPU du **HP DL20 Gen9** ne supportent pas le **SR-IOV**. Ça m'oblige actuellement à faire tourner les fonctions de routage réseau directement sur l'hyperviseur.
La fonction SR-IOV me permettrait de mettre le {{<internalLink path="/blog/fibre-orange-remplacer-livebox-routeur-centos-stream-8" title="routeur CentOS Stream" >}} dans une VM sans perte de performance. La fonction SR-IOV me permettrait de mettre le [routeur CentOS Stream](/blog/fibre-orange-remplacer-livebox-routeur-centos-stream-8) dans une VM sans perte de performance.
En résumé, j'ai besoin de : En résumé, j'ai besoin de :
@ -143,7 +143,7 @@ Il sera alors toujours temps de passer sur un CPU plus puissant.
Le CPU **Ampere Altra Q64-22** a un TDP de 69 W d'après la [fiche technique Ampere Altra](https://d1o0i0v5q5lp8h.cloudfront.net/ampere/live/assets/documents/Altra_Rev_A1_DS_v1.27_20220331.pdf). Le CPU **Ampere Altra Q64-22** a un TDP de 69 W d'après la [fiche technique Ampere Altra](https://d1o0i0v5q5lp8h.cloudfront.net/ampere/live/assets/documents/Altra_Rev_A1_DS_v1.27_20220331.pdf).
{{< attachedFigure src="ampere-altra-q64-22.jpeg" title="Le CPU Ampere Altra Q64-22, monté sur la carte mère ASRock Rack ALTRAD8UD-1L2T" >}} ![Le CPU Ampere Altra Q64-22, monté sur la carte mère ASRock Rack ALTRAD8UD-1L2T](ampere-altra-q64-22.jpeg "Le CPU Ampere Altra Q64-22, monté sur la carte mère ASRock Rack ALTRAD8UD-1L2T")
Autre difficulté, c'est que j'habite en France et Newegg ne livre pas en France... Autre difficulté, c'est que j'habite en France et Newegg ne livre pas en France...
J'ai donc dû passer par un *reshipper* : une entreprise aux États-Unis qui réceptionne les colis et les réexpédie partout dans le monde. J'ai donc dû passer par un *reshipper* : une entreprise aux États-Unis qui réceptionne les colis et les réexpédie partout dans le monde.
@ -186,7 +186,7 @@ Avant de me décider à acheter le boîtier [Innovision M24306](http://iovserver
Le boîtier **Innovision M24306** est trouvable sur [Aliexpress](https://www.aliexpress.us/item/1005005856237609.html) et [Alibaba](https://www.alibaba.com/product-detail/Ultra-Short-2U-rackmount-Server-Chassis_1600636420062.html). Le boîtier **Innovision M24306** est trouvable sur [Aliexpress](https://www.aliexpress.us/item/1005005856237609.html) et [Alibaba](https://www.alibaba.com/product-detail/Ultra-Short-2U-rackmount-Server-Chassis_1600636420062.html).
{{< attachedFigure src="innovision-m24306.jpeg" title="Le boîtier Innovision M24306." >}} ![Le boîtier Innovision M24306.](innovision-m24306.jpeg "Le boîtier Innovision M24306.")
Quelques remarques utiles (j'ai demandé des clarifications avant de passer commande) : Quelques remarques utiles (j'ai demandé des clarifications avant de passer commande) :
@ -199,7 +199,7 @@ Quelques remarques utiles (j'ai demandé des clarifications avant de passer comm
Note : la prise VGA que l'on voit sur l'oreille droite du serveur n'est connectée à rien du tout. Note : la prise VGA que l'on voit sur l'oreille droite du serveur n'est connectée à rien du tout.
Mais ce n'est pas gênant dans le sens où le boîtier a ses entrées/sorties sur la face avant donc la prise VGA est déjà sur la face avant et n'a pas besoin d'être déportée. Mais ce n'est pas gênant dans le sens où le boîtier a ses entrées/sorties sur la face avant donc la prise VGA est déjà sur la face avant et n'a pas besoin d'être déportée.
{{< attachedFigure src="vga-not-connected.jpeg" title="La prise VGA sur l'oreille droite du boîtier Innovision M24306 n'est pas connectée." >}} ![La prise VGA sur l'oreille droite du boîtier Innovision M24306 n'est pas connectée.](vga-not-connected.jpeg "La prise VGA sur l'oreille droite du boîtier Innovision M24306 n'est pas connectée.")
## Alimentation CRPS FSP ## Alimentation CRPS FSP
@ -227,7 +227,7 @@ Le modèle [FSP-FC250](https://www.fsp-group.com/download/pro/FSP-FC250_Datashee
J'ai ensuite opté pour le module d'alimentation de la plus petite puissance disponible car 2 x 550W, ça fait déjà beaucoup pour un petit serveur ! J'ai ensuite opté pour le module d'alimentation de la plus petite puissance disponible car 2 x 550W, ça fait déjà beaucoup pour un petit serveur !
C'est le modèle [FSP550-20FM](https://www.fsp-group.com/download/pro/FSP550-20FM_Datasheet.pdf). C'est le modèle [FSP550-20FM](https://www.fsp-group.com/download/pro/FSP550-20FM_Datasheet.pdf).
{{< attachedFigure src="psu-fsp.webp" title="La composition de l'alimentation CRPS de marque FSP." >}} ![La composition de l'alimentation CRPS de marque FSP.](psu-fsp.webp "La composition de l'alimentation CRPS de marque FSP.")
Il est à noter que la *Power Distribution Board* n'est fixée au boîtier que par deux petites vis. Il est à noter que la *Power Distribution Board* n'est fixée au boîtier que par deux petites vis.
Pour éviter qu'elle ne bouge, j'ai dû la caler avec un morceau de mousse. Pour éviter qu'elle ne bouge, j'ai dû la caler avec un morceau de mousse.
@ -256,7 +256,7 @@ La référence **CAB-8654/8654-8i-11-P0.5M-85** chez [10Gtek](https://www.10gtek
- Enfin, il est possible d'opter pour des connecteurs coudés ou droits. - Enfin, il est possible d'opter pour des connecteurs coudés ou droits.
J'ai pris les **connecteurs droits**. J'ai pris les **connecteurs droits**.
{{< attachedFigure src="slimsas-cables.webp" title="Cheminement des câbles SlimSAS / SFF-8654 dans le boîtier Innovision M24306." >}} ![Cheminement des câbles SlimSAS / SFF-8654 dans le boîtier Innovision M24306.](slimsas-cables.webp "Cheminement des câbles SlimSAS / SFF-8654 dans le boîtier Innovision M24306.")
## Stockage ## Stockage
@ -270,7 +270,7 @@ Ces SSD sont au format *Add-in Card (AIC)*, demi-hauteur, demi-longueur (**HHHL*
Mais dans l'immédiat, ça me permet d'avancer. Mais dans l'immédiat, ça me permet d'avancer.
Et si je trouve une enchère eBay avec un lot de SSD Samsung PM1733 à bon prix, je n'aurais qu'à transférer les données sur les nouveaux SSD pour récupérer 3 emplacements PCIe. Et si je trouve une enchère eBay avec un lot de SSD Samsung PM1733 à bon prix, je n'aurais qu'à transférer les données sur les nouveaux SSD pour récupérer 3 emplacements PCIe.
{{< attachedFigure src="nvme-storage.jpeg" title="Trois SSD Samsung PM1735 et un SSD Samsung 980 PRO installés dans le boîtier." >}} ![Trois SSD Samsung PM1735 et un SSD Samsung 980 PRO installés dans le boîtier.](nvme-storage.jpeg "Trois SSD Samsung PM1735 et un SSD Samsung 980 PRO installés dans le boîtier.")
Il est à noter que les SSD **Samsung PM1733A** ont été testés par la société **Ampere Computing** et ont montré de bonnes performances avec leur CPU ! Voir [Samsung FIO Performance - Solution Brief](https://amperecomputing.com/briefs/samsung-FIO-performance). Il est à noter que les SSD **Samsung PM1733A** ont été testés par la société **Ampere Computing** et ont montré de bonnes performances avec leur CPU ! Voir [Samsung FIO Performance - Solution Brief](https://amperecomputing.com/briefs/samsung-FIO-performance).
@ -278,7 +278,7 @@ J'ai ajouté à cela un SSD NVMe au format M.2 pour héberger le système d'expl
Par soucis du détail, j'ai quand même testé 6 SSD NVMe au format U.2 (PCIe 3.0) sur le *backplane NVMe* du boîtier. Ça fonctionne ! En revanche, je n'ai pas testé le branchage/débranchage à chaud (*hotplug*) car je ne pense pas que le *backplane* de ce boîtier ait implémenté la fonctionnalité. Par soucis du détail, j'ai quand même testé 6 SSD NVMe au format U.2 (PCIe 3.0) sur le *backplane NVMe* du boîtier. Ça fonctionne ! En revanche, je n'ai pas testé le branchage/débranchage à chaud (*hotplug*) car je ne pense pas que le *backplane* de ce boîtier ait implémenté la fonctionnalité.
{{< attachedFigure src="more-nvme-storage.webp" title="Six SSD NVMe de plus, pour un total de 26.4 To de stockage NVMe !" >}} ![Six SSD NVMe de plus, pour un total de 26.4 To de stockage NVMe !](more-nvme-storage.webp "Six SSD NVMe de plus, pour un total de 26.4 To de stockage NVMe !")
## Nomenclature ## Nomenclature
@ -306,7 +306,7 @@ Et dans ce prix, je n'ai pas compté les frais de port, les droits de douane, et
Il est à noter que, par défaut, le flux d'air des ventilateurs des alimentations **FSP 550-20FM** et du boîtier **Innovision M24306** sont inversés : les ventilateurs des alimentations soufflent dans le sens **avant -> arrière** alors que les ventilateurs du boîtier soufflent dans le sens **arrière -> avant**. Il est à noter que, par défaut, le flux d'air des ventilateurs des alimentations **FSP 550-20FM** et du boîtier **Innovision M24306** sont inversés : les ventilateurs des alimentations soufflent dans le sens **avant -> arrière** alors que les ventilateurs du boîtier soufflent dans le sens **arrière -> avant**.
Fort heureusement, les ventilateurs du boîtier sont réversibles. Fort heureusement, les ventilateurs du boîtier sont réversibles.
{{< attachedFigure src="fans-air-flow.webp" title="J'ai retourné les quatre ventilateurs du avoir un flux d'air avant -> arrière. Dans cette configuration, les étiquettes des ventilateurs ne sont pas visibles de l'intérieur du boîtier." >}} ![J'ai retourné les quatre ventilateurs du avoir un flux d'air avant -> arrière. Dans cette configuration, les étiquettes des ventilateurs ne sont pas visibles de l'intérieur du boîtier.](fans-air-flow.webp "J'ai retourné les quatre ventilateurs du avoir un flux d'air avant -> arrière. Dans cette configuration, les étiquettes des ventilateurs ne sont pas visibles de l'intérieur du boîtier.")
Je n'ai pas encore fait de test de performance, mais les températures mesurées sont plus basses avec ce flux d'air optimisé. Je n'ai pas encore fait de test de performance, mais les températures mesurées sont plus basses avec ce flux d'air optimisé.
C'est au niveau des 6 SSD NVMe à gauche que l'effet est le plus notable. C'est au niveau des 6 SSD NVMe à gauche que l'effet est le plus notable.
@ -319,15 +319,15 @@ Les trois emplacements inutilisés peuvent être masqués pour forcer le flux d'
L'aspect extérieur du produit est plutôt flatteur, presque professionnel. L'aspect extérieur du produit est plutôt flatteur, presque professionnel.
{{< attachedFigure src="final-product.webp" title="Serveur 2U basé sur un boîtier Innovision M24306, une carte mère ASRock Rack ALTRAD8UD-1L2T et un CPU Ampere Altra Q64-22. Face avant en haut, face arrière en bas." >}} ![Serveur 2U basé sur un boîtier Innovision M24306, une carte mère ASRock Rack ALTRAD8UD-1L2T et un CPU Ampere Altra Q64-22. Face avant en haut, face arrière en bas.](final-product.webp "Serveur 2U basé sur un boîtier Innovision M24306, une carte mère ASRock Rack ALTRAD8UD-1L2T et un CPU Ampere Altra Q64-22. Face avant en haut, face arrière en bas.")
L'intérieur est moins bien agencé qu'un serveur HP ou Dell mais je saurais m'en satisfaire. 😎 L'intérieur est moins bien agencé qu'un serveur HP ou Dell mais je saurais m'en satisfaire. 😎
{{< attachedFigure src="internal-layout.webp" title="Agencement interne du boîtier Innovision M24306 avec une carte mère ASRock Rack ALTRAD8UD-1L2T et un CPU Ampere Altra Q64-22." >}} ![Agencement interne du boîtier Innovision M24306 avec une carte mère ASRock Rack ALTRAD8UD-1L2T et un CPU Ampere Altra Q64-22.](internal-layout.webp "Agencement interne du boîtier Innovision M24306 avec une carte mère ASRock Rack ALTRAD8UD-1L2T et un CPU Ampere Altra Q64-22.")
Dernières photos, cette fois-ci avec le couvercle en place, prêt à être racké ! 🚀 Dernières photos, cette fois-ci avec le couvercle en place, prêt à être racké ! 🚀
{{< attachedFigure src="perspective-view.webp" title="Boîtier Innovision M24306 avec une carte mère ASRock Rack ALTRAD8UD-1L2T et un CPU Ampere Altra Q64-22." >}} ![Boîtier Innovision M24306 avec une carte mère ASRock Rack ALTRAD8UD-1L2T et un CPU Ampere Altra Q64-22.](perspective-view.webp "Boîtier Innovision M24306 avec une carte mère ASRock Rack ALTRAD8UD-1L2T et un CPU Ampere Altra Q64-22.")
## Conclusion ## Conclusion

14
content/french/blog/installation-mise-jour-openshift-environnement-deconnecte/index.md

@ -150,7 +150,7 @@ En effet, ce fichier openshift-install n'est valide que pour une install de **ce
Créer le fichier install-config.yaml comme pour une installation connectée, en fonction de vos choix d'architecture et de votre environnement. Créer le fichier install-config.yaml comme pour une installation connectée, en fonction de vos choix d'architecture et de votre environnement.
Ajouter la section **imageContentSources** comme indiqué ci-dessus. Ajouter la section **imageContentSources** comme indiqué ci-dessus.
{{< highlight yaml "hl_lines=26-34" >}} ```yaml {hl_lines="26-34"}
apiVersion: v1 apiVersion: v1
baseDomain: itix.xyz baseDomain: itix.xyz
compute: compute:
@ -185,7 +185,7 @@ imageContentSources:
- registry.itix.xyz/openshift-mirror/ocp4 - registry.itix.xyz/openshift-mirror/ocp4
- registry.itix.xyz/openshift-mirror/ocp4-release - registry.itix.xyz/openshift-mirror/ocp4-release
source: quay.io/openshift-release-dev/ocp-v4.0-art-dev source: quay.io/openshift-release-dev/ocp-v4.0-art-dev
{{< / highlight >}} ```
Puis lancer l'installation comme pour une installation connectée. Puis lancer l'installation comme pour une installation connectée.
@ -316,11 +316,11 @@ EOF
Créer un fichier **Dockerfile** avec le contenu suivant. Créer un fichier **Dockerfile** avec le contenu suivant.
{{< highlightFile "Dockerfile" "docker" "" >}} ```docker {filename="Dockerfile"}
FROM registry.access.redhat.com/ubi8/ubi:8.1 FROM registry.access.redhat.com/ubi8/ubi:8.1
RUN curl -L -o cincinnati-graph-data.tar.gz https://github.com/openshift/cincinnati-graph-data/archive/master.tar.gz RUN curl -L -o cincinnati-graph-data.tar.gz https://github.com/openshift/cincinnati-graph-data/archive/master.tar.gz
CMD exec /bin/bash -c "tar xvzf cincinnati-graph-data.tar.gz -C /var/lib/cincinnati/graph-data/ --strip-components=1" CMD exec /bin/bash -c "tar xvzf cincinnati-graph-data.tar.gz -C /var/lib/cincinnati/graph-data/ --strip-components=1"
{{< / highlightFile >}} ```
Construire l'image correspondante et la pousser dans la registry privée. Construire l'image correspondante et la pousser dans la registry privée.
@ -369,15 +369,15 @@ oc patch clusterversion version -p "{\"spec\":{\"upstream\":\"${POLICY_ENGINE_GR
Pour préparer la mise à jour, j'utilise le [Red Hat OpenShift Container Platform Update Graph](https://access.redhat.com/labs/ocpupgradegraph/update_path). Pour préparer la mise à jour, j'utilise le [Red Hat OpenShift Container Platform Update Graph](https://access.redhat.com/labs/ocpupgradegraph/update_path).
{{< attachedFigure src="update-graph-1.png" title="Saisir la version source, la version cible et le canal de mise à jour." >}} ![Saisir la version source, la version cible et le canal de mise à jour.](update-graph-1.png "Saisir la version source, la version cible et le canal de mise à jour.")
Il m'a permis de découvrir que le channel **fast-4.8** me permet de passer de la 4.7.10 à la 4.8.5 avec une seule étape intermédiaire: la 4.7.24. Il m'a permis de découvrir que le channel **fast-4.8** me permet de passer de la 4.7.10 à la 4.8.5 avec une seule étape intermédiaire: la 4.7.24.
{{< attachedFigure src="update-graph-2.png" title="En utilisant le canal fast-4.8, je peux passer de la 4.7.10 à la 4.7.24 à la 4.8.5." >}} ![En utilisant le canal fast-4.8, je peux passer de la 4.7.10 à la 4.7.24 à la 4.8.5.](update-graph-2.png "En utilisant le canal fast-4.8, je peux passer de la 4.7.10 à la 4.7.24 à la 4.8.5.")
L'outil génère aussi un graphe contenant toutes les versions disponibles et les chemins autorisés entre ces versions. L'outil génère aussi un graphe contenant toutes les versions disponibles et les chemins autorisés entre ces versions.
{{< attachedFigure src="update-graph-3.png" title="Le graphe de mise à jour résultant." >}} ![Le graphe de mise à jour résultant.](update-graph-3.png "Le graphe de mise à jour résultant.")
Nous effectuerons donc une mise à jour en deux étapes: d'abord une mise à jour vers la 4.7.24, puis vers la 4.8.5. Nous effectuerons donc une mise à jour en deux étapes: d'abord une mise à jour vers la 4.7.24, puis vers la 4.8.5.

1
content/french/opensource/_index.md

@ -1,5 +1,6 @@
--- ---
title: "Open Source" title: "Open Source"
description: "Les projets Open Source abordés sur ce site : OpenShift, Keycloak, 3scale, Podman, Ansible, etc."
--- ---
J'utilise dans mon travail au quotidien de nombreuses communautés Open Source, sur lesquelles j'ai été ammené à écrire des articles. J'utilise dans mon travail au quotidien de nombreuses communautés Open Source, sur lesquelles j'ai été ammené à écrire des articles.

4
content/french/search/index.md

@ -2,4 +2,8 @@
title: "Recherche" title: "Recherche"
type: search type: search
layout: search layout: search
# The search index is generated as a separate JSON file (see config.yaml)
outputs:
- html
- searchindex
--- ---

1
content/french/speaking/_index.md

@ -1,5 +1,6 @@
--- ---
title: "Conférences" title: "Conférences"
description: "Conférences, ateliers et démos présentés par Nicolas Massé lors de salons et d'événements clients."
--- ---
Je suis occasionnellement invité chez des clients ou sur des salons pour parler des APIs et de SSO. Je suis occasionnellement invité chez des clients ou sur des salons pour parler des APIs et de SSO.

2
content/french/speaking/demobox-redhat-sso/index.md

@ -14,7 +14,7 @@ Au travers d'une série de trois vidéos, je présente Red Hat Single Sign On (l
Je commence par l'installation, la fédération des identités, puis vient la sécurisation d'une application NodeJS. Je commence par l'installation, la fédération des identités, puis vient la sécurisation d'une application NodeJS.
Et enfin, on termine par la sécurisation d'une application Quarkus. Et enfin, on termine par la sécurisation d'une application Quarkus.
{{< screenshotOf src="tweet-RedHatFrance.png" href="https://twitter.com/RedHatFrance/status/1390660299011895296" >}} [![Tweet de @RedHatFrance](tweet-RedHatFrance.png)](https://twitter.com/RedHatFrance/status/1390660299011895296)
Retrouvez ci-dessous tous les liens vers les vidéos ! Retrouvez ci-dessous tous les liens vers les vidéos !

21
content/french/speaking/devoxx-france-2019/index.md

@ -20,15 +20,18 @@ Le replay est sur la [chaîne YouTube de DevoxxFR](https://www.youtube.com/watch
## Photos ## Photos
{{< attachedFigure src="on-stage-1.jpg" >}} ![](on-stage-1.jpg)
{{< attachedFigure src="on-stage-2.jpg" >}}
{{< attachedFigure src="on-stage-4.jpg" >}} ![](on-stage-2.jpg)
{{< attachedFigure src="on-stage-5.jpg" >}}
![](on-stage-4.jpg)
![](on-stage-5.jpg)
## Réactions du public ## Réactions du public
{{< screenshotOf src="tweet-lbroudoux.png" href="https://twitter.com/lbroudoux/status/1119221025466720256" >}} [![Tweet de @lbroudoux](tweet-lbroudoux.png)](https://twitter.com/lbroudoux/status/1119221025466720256)
{{< screenshotOf src="tweet-gbloquel.png" href="https://twitter.com/gbloquel/status/1119170710696493056" >}} [![Tweet de @gbloquel](tweet-gbloquel.png)](https://twitter.com/gbloquel/status/1119170710696493056)
{{< screenshotOf src="tweet-sebi2706.png" href="https://twitter.com/sebi2706/status/1119169813392314368" >}} [![Tweet de @sebi2706](tweet-sebi2706.png)](https://twitter.com/sebi2706/status/1119169813392314368)
{{< screenshotOf src="tweet-cgodard.png" href="https://twitter.com/cgodard/status/1119159971835289600" >}} [![Tweet de @cgodard](tweet-cgodard.png)](https://twitter.com/cgodard/status/1119159971835289600)
{{< screenshotOf src="tweet-PetitMel_issa.png" href="https://twitter.com/PetitMel_issa/status/1119170065289678848" >}} [![Tweet de @PetitMel_issa](tweet-PetitMel_issa.png)](https://twitter.com/PetitMel_issa/status/1119170065289678848)

8
content/french/speaking/devoxx-france-2025/index.md

@ -25,15 +25,15 @@ Un moment fort, mêlant technologie, fun et culture geek, que j’ai voulu parta
## Mission IApossible: Arrêtez le train Lego à temps grâce à l’IA et l’Edge ## Mission IApossible: Arrêtez le train Lego à temps grâce à l’IA et l’Edge
{{< attachedFigure src="on-stage1.jpeg" title="Mise en place de la démo terminée, on peut commencer !" >}} ![Mise en place de la démo terminée, on peut commencer !](on-stage1.jpeg "Mise en place de la démo terminée, on peut commencer !")
Ma session portait le titre évocateur "[Mission IApossible: Arrêtez le train Lego à temps grâce à l’IA et l’Edge](https://www.devoxx.fr/agenda-2025/talk/mission-iapossible-arretez-le-train-lego-a-temps-grace-a-l-ia-et-l-edge/)". L’objectif ? Montrer de manière ludique et technique comment des projets mélant **Intelligence Artificielle** et **Edge Computing** être réalisés avec les technologies [Red Hat](https://www.redhat.com/fr). Ma session portait le titre évocateur "[Mission IApossible: Arrêtez le train Lego à temps grâce à l’IA et l’Edge](https://www.devoxx.fr/agenda-2025/talk/mission-iapossible-arretez-le-train-lego-a-temps-grace-a-l-ia-et-l-edge/)". L’objectif ? Montrer de manière ludique et technique comment des projets mélant **Intelligence Artificielle** et **Edge Computing** être réalisés avec les technologies [Red Hat](https://www.redhat.com/fr).
{{< attachedFigure src="quote.png" >}} ![](quote.png)
Imaginez sur scène : un train **Lego City #60337**, boosté avec un **Jetson Orin** de Nvidia, embarque une IA capable de reconnaître les panneaux de signalisation et… de s’arrêter juste à temps. Dans le rôle de chef d’orchestre ? Des technologies comme **YOLO** pour la détection d’objets, **Jupyter** pour l’expérimentation, **Quarkus** pour l’application embarquée, et **Tekton Pipelines** pour la chaîne d'intégration continue et **microshift** pour le déploiement. Bref, un condensé d’innovation dans un décor Lego, avec pour toile de fond l’univers de Mission Impossible. Imaginez sur scène : un train **Lego City #60337**, boosté avec un **Jetson Orin** de Nvidia, embarque une IA capable de reconnaître les panneaux de signalisation et… de s’arrêter juste à temps. Dans le rôle de chef d’orchestre ? Des technologies comme **YOLO** pour la détection d’objets, **Jupyter** pour l’expérimentation, **Quarkus** pour l’application embarquée, et **Tekton Pipelines** pour la chaîne d'intégration continue et **microshift** pour le déploiement. Bref, un condensé d’innovation dans un décor Lego, avec pour toile de fond l’univers de Mission Impossible.
{{< attachedFigure src="train.jpeg" title="La mise en place du train sur scène en seulement 20 minutes, c'était ça la mission impossible !" >}} ![La mise en place du train sur scène en seulement 20 minutes, c'était ça la mission impossible !](train.jpeg "La mise en place du train sur scène en seulement 20 minutes, c'était ça la mission impossible !")
Au-delà de l’aspect ludique, l’enjeu était aussi de montrer la capacité d'embarquer un modèle d'intelligence artificielle à l'Edge, dans un cadre concret et tangible. Car oui, ce genre de cas d’usage s’applique bien au-delà du monde Lego : smart cities, véhicules autonomes, automatisation industrielle… les possibilités sont multiples. Au-delà de l’aspect ludique, l’enjeu était aussi de montrer la capacité d'embarquer un modèle d'intelligence artificielle à l'Edge, dans un cadre concret et tangible. Car oui, ce genre de cas d’usage s’applique bien au-delà du monde Lego : smart cities, véhicules autonomes, automatisation industrielle… les possibilités sont multiples.
@ -47,4 +47,4 @@ Un succès que j’ai eu un immense plaisir à partager avec une salle bien remp
## Ressources ## Ressources
- [Le code source des pipelines Tekton multi-architecture](https://github.com/nmasse-itix/tekton-pipeline-multiarch/tree/devoxx-france-2025) - [Le code source des pipelines Tekton multi-architecture](https://github.com/nmasse-itix/tekton-pipeline-multiarch/tree/devoxx-france-2025)
- [Les supports de présentation]({{< attachedFileLink src="slides.pdf" >}}) - [Les supports de présentation](slides.pdf)

8
content/french/speaking/mad-roadshow-paris-2023/index.md

@ -14,7 +14,7 @@ opensource:
Le 6 Juin 2023, j'ai participé à l'événement [MAD Roadshow Paris 2023](https://events.redhat.com/profile/form/index.cfm?PKformID=0x784359abcd&sc_cid=7013a0000034l4oAAA) durant lequel j'ai montré une démo intitulée "Ease app development and deployment - From a laptop to production, the great and easy way". Le 6 Juin 2023, j'ai participé à l'événement [MAD Roadshow Paris 2023](https://events.redhat.com/profile/form/index.cfm?PKformID=0x784359abcd&sc_cid=7013a0000034l4oAAA) durant lequel j'ai montré une démo intitulée "Ease app development and deployment - From a laptop to production, the great and easy way".
{{< attachedFigure src="slide.png" >}} ![](slide.png)
La démonstration combine de nombreux outils de la plateforme [Red Hat OpenShift](https://www.redhat.com/en/technologies/cloud-computing/openshift) : La démonstration combine de nombreux outils de la plateforme [Red Hat OpenShift](https://www.redhat.com/en/technologies/cloud-computing/openshift) :
@ -31,5 +31,7 @@ Le code de la démo est disponible sur GitHub :
Quelques photos de l'évènement : Quelques photos de l'évènement :
{{< attachedFigure src="20230606_114542.jpeg" >}} ![](20230606_114542.jpeg)
{{< attachedFigure src="20230606_114804.jpeg" >}}
![](20230606_114804.jpeg)

10
content/french/speaking/matinale-it-for-business-devsecops-2022/index.md

@ -33,8 +33,8 @@ Quelques constats chiffrés :
Mon intervention a été reprise par les journalistes dans certains tweets : Mon intervention a été reprise par les journalistes dans certains tweets :
{{< screenshotOf src="tweet-ITforB-01.png" href="https://twitter.com/ITforB/status/1544241974848487425" >}} [![Tweet de @ITforB](tweet-ITforB-01.png)](https://twitter.com/ITforB/status/1544241974848487425)
{{< screenshotOf src="tweet-ITforB-02.png" href="https://twitter.com/thierryderouet/status/1544240686303744002" >}} [![Tweet de @ITforB](tweet-ITforB-02.png)](https://twitter.com/thierryderouet/status/1544240686303744002)
{{< screenshotOf src="tweet-ITforB-03.png" href="https://twitter.com/thierryderouet/status/1544240966097375233" >}} [![Tweet de @ITforB](tweet-ITforB-03.png)](https://twitter.com/thierryderouet/status/1544240966097375233)
{{< screenshotOf src="tweet-ITforB-04.png" href="https://twitter.com/thierryderouet/status/1544241464326201345" >}} [![Tweet de @ITforB](tweet-ITforB-04.png)](https://twitter.com/thierryderouet/status/1544241464326201345)
{{< screenshotOf src="tweet-ITforB-05.png" href="https://twitter.com/thierryderouet/status/1544241708271116289" >}} [![Tweet de @ITforB](tweet-ITforB-05.png)](https://twitter.com/thierryderouet/status/1544241708271116289)

6
content/french/speaking/openshift-ai-roadshow-2024/index.md

@ -20,11 +20,11 @@ La démo est basée sur le scénario du 7ème opus de la saga **Mission Impossib
**Ethan Hunt** a besoin d'aide pour arrêter le train. **Ethan Hunt** a besoin d'aide pour arrêter le train.
Grâce un modèle d'intelligence artificielle conçu dans **OpenShift AI** et déployé sur un **Nvidia Jetson Orin** faisant tourner **Red Hat Device Edge**, le train reconnait les panneaux de signalisation et s'arrête tout seul ! Grâce un modèle d'intelligence artificielle conçu dans **OpenShift AI** et déployé sur un **Nvidia Jetson Orin** faisant tourner **Red Hat Device Edge**, le train reconnait les panneaux de signalisation et s'arrête tout seul !
{{< attachedFigure src="le-train-lego.jpeg" title="Le train Lego équipé de la webcam, du Nvidia Jetson Orin et de la batterie." >}} ![Le train Lego équipé de la webcam, du Nvidia Jetson Orin et de la batterie.](le-train-lego.jpeg "Le train Lego équipé de la webcam, du Nvidia Jetson Orin et de la batterie.")
Et c'est à l'occasion de cette démo que j'ai écrit un article sur intitulé {{< internalLink path="/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-aws/index.md" >}}. Et c'est à l'occasion de cette démo que j'ai écrit un article sur intitulé [](/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-aws/index.md).
{{< attachedFigure src="train-console-1.png" title="Vue de la caméra embarquée du train avec les résultats de l'inférence." >}} ![Vue de la caméra embarquée du train avec les résultats de l'inférence.](train-console-1.png "Vue de la caméra embarquée du train avec les résultats de l'inférence.")
La démo est en train d'être affinée pour être présentée dans les prochains événements Red Hat. La démo est en train d'être affinée pour être présentée dans les prochains événements Red Hat.
Venez nous voir ! Venez nous voir !

12
content/french/speaking/platform-day-2024/index.md

@ -20,7 +20,7 @@ Le 28 Mai 2024, j'ai participé à l'événement [Platform Day](https://events.r
La démo mets en scène l'équipe du film **Mission Impossible** qui a fait une halte inattendue au Pavillon Royal pour offrir aux invités un aperçu de l'une des scènes les plus audacieuses du nouvel épisode. La démo mets en scène l'équipe du film **Mission Impossible** qui a fait une halte inattendue au Pavillon Royal pour offrir aux invités un aperçu de l'une des scènes les plus audacieuses du nouvel épisode.
Le film met en vedette Ethan Hunt et son équipe, qui doivent déjouer un complot visant à prendre le contrôle des Kubernetes de la planète. 😄 Le film met en vedette Ethan Hunt et son équipe, qui doivent déjouer un complot visant à prendre le contrôle des Kubernetes de la planète. 😄
{{< attachedFigure src="equipe.jpeg" title="L'équipe en charge de la démo Mission Impossible." >}} ![L'équipe en charge de la démo Mission Impossible.](equipe.jpeg "L'équipe en charge de la démo Mission Impossible.")
Les personnages principaux de cette mission sont : Les personnages principaux de cette mission sont :
@ -41,7 +41,7 @@ Dans cette mission, mon rôle a consisté à **prendre le contrôle du train**.
Le train est équipé d’un moteur et d’un Hub Lego. Le train est équipé d’un moteur et d’un Hub Lego.
Le Hub Lego reçoit les ordres d’accélération, décélération et freinage via le protocole **Bluetooth Low Energy**. Le Hub Lego reçoit les ordres d’accélération, décélération et freinage via le protocole **Bluetooth Low Energy**.
{{< attachedFigure src="architecture-materielle.png" title="L'architecture matérielle mise en oeuvre dans la démonstration." >}} ![L'architecture matérielle mise en oeuvre dans la démonstration.](architecture-materielle.png "L'architecture matérielle mise en oeuvre dans la démonstration.")
Nous avons intégré une carte **Nvidia Jetson Orin** dans le train Lego. Nous avons intégré une carte **Nvidia Jetson Orin** dans le train Lego.
La carte Nvidia Jetson Orin est un *System on Chip* (SoC) qui intègre tous les composants nécessaires à notre mission : CPU, RAM, stockage et un puissant GPU pour accélérer les calculs. La carte Nvidia Jetson Orin est un *System on Chip* (SoC) qui intègre tous les composants nécessaires à notre mission : CPU, RAM, stockage et un puissant GPU pour accélérer les calculs.
@ -60,7 +60,7 @@ Le flux vidéo est relayé depuis le Jetson via un **broker Kafka**.
De plus, des pipelines MLOps sont mis en place pour entraîner le modèle d'intelligence artificielle, ainsi que des pipelines CI/CD pour construire les images de conteneurs de nos microservices pour les architectures x86 et ARM. De plus, des pipelines MLOps sont mis en place pour entraîner le modèle d'intelligence artificielle, ainsi que des pipelines CI/CD pour construire les images de conteneurs de nos microservices pour les architectures x86 et ARM.
{{< attachedFigure src="mission-edge.png" title="Les points clés de ma mission." >}} ![Les points clés de ma mission.](mission-edge.png "Les points clés de ma mission.")
Durant cette mission, j'ai fait face à trois principaux défis. Durant cette mission, j'ai fait face à trois principaux défis.
@ -88,7 +88,7 @@ Il suffit d'ajouter la dépendance vers la bibliothèque **node-poweredup** patc
} }
``` ```
{{< attachedFigure src="challenges-edge.png" title="Les défis que j'ai pu rencontrer lors de ma mission." >}} ![Les défis que j'ai pu rencontrer lors de ma mission.](challenges-edge.png "Les défis que j'ai pu rencontrer lors de ma mission.")
Le **second défi** a été de préparer des images du système d'exploitation (**Red Hat Enterprise Linux 9**), adaptées pour la carte **Nvidia Jetson Orin Nano**. Le **second défi** a été de préparer des images du système d'exploitation (**Red Hat Enterprise Linux 9**), adaptées pour la carte **Nvidia Jetson Orin Nano**.
Pour cela, j'ai utilisé l'outil [composer-cli](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/composing_installing_and_managing_rhel_for_edge_images/composing-a-rhel-for-edge-image-using-image-builder-command-line_composing-installing-managing-rhel-for-edge-images#network_based_deployments_workflow). Pour cela, j'ai utilisé l'outil [composer-cli](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/composing_installing_and_managing_rhel_for_edge_images/composing-a-rhel-for-edge-image-using-image-builder-command-line_composing-installing-managing-rhel-for-edge-images#network_based_deployments_workflow).
@ -104,7 +104,7 @@ Il a donc fallu récupérer auprès de l'*engineering* RHEL les modules noyau Nv
Le **dernier défi** a été de concevoir des **pipelines CI/CD** pour créer des images de conteneur multi-architecture. Le **dernier défi** a été de concevoir des **pipelines CI/CD** pour créer des images de conteneur multi-architecture.
En effet, les puces M1 d'Apple ont une architecture **arm64**, les PC sont en **x86_64**. En effet, les puces M1 d'Apple ont une architecture **arm64**, les PC sont en **x86_64**.
Idem, les tests d'intégration sont souvent exécutés sur des serveurs dans le cloud (**x86_64**) alors que le déploiement se fait sur la carte Nvidia Jetson Orin Nano (**arm64**). Idem, les tests d'intégration sont souvent exécutés sur des serveurs dans le cloud (**x86_64**) alors que le déploiement se fait sur la carte Nvidia Jetson Orin Nano (**arm64**).
J'ai détaillé la procédure complète dans l'article intitulé {{< internalLink path="/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-aws/index.md" >}}. J'ai détaillé la procédure complète dans l'article intitulé [](/blog/build-multi-architecture-container-images-with-kubernetes-buildah-tekton-aws/index.md).
## Genius Bar ## Genius Bar
@ -112,7 +112,7 @@ L'après-midi, nous avons animé un atelier *Genius Bar*, où beaucoup de partic
L'afflux était tel que nous n'avons pas trouvé 5 minutes pour démarrer le train. L'afflux était tel que nous n'avons pas trouvé 5 minutes pour démarrer le train.
C'était intense ! 🥵 C'était intense ! 🥵
{{< attachedFigure src="genius-bar.jpeg" title="Le Genius Bar de la démo Mission Impossible." >}} ![Le Genius Bar de la démo Mission Impossible.](genius-bar.jpeg "Le Genius Bar de la démo Mission Impossible.")
## Conclusion ## Conclusion

6
content/french/speaking/red-hat-allie-strategie-edge/index.md

@ -11,12 +11,12 @@ Le jeudi 28 mars à 10h, j'ai animé un webinaire sur le thème du *Edge Computi
<!--more--> <!--more-->
{{< screenshotOf src="tweet-RedHatFrance.png" href="https://twitter.com/RedHatFrance/status/1768201675481698412" >}} [![Tweet de @RedHatFrance](tweet-RedHatFrance.png)](https://twitter.com/RedHatFrance/status/1768201675481698412)
Lors du webinaire, une introduction à l'*Edge Computing* a été présentée, suivie d'une exploration des solutions spécifiques de Red Hat pour cette technologie. Lors du webinaire, une introduction à l'*Edge Computing* a été présentée, suivie d'une exploration des solutions spécifiques de Red Hat pour cette technologie.
La session s'est achevée avec un cas client. La session s'est achevée avec un cas client.
{{< attachedFigure src="agenda.png" >}} ![](agenda.png)
Bien que l'*Edge Computing* ne soit pas un concept nouveau, il continue de gagner en popularité grâce à sa mise en oeuvre dans divers secteurs tels que les télécommunications, le commerce de détail et l'industrie. Bien que l'*Edge Computing* ne soit pas un concept nouveau, il continue de gagner en popularité grâce à sa mise en oeuvre dans divers secteurs tels que les télécommunications, le commerce de détail et l'industrie.
Cette technologie se caractérise par le déploiement d'applications au plus près des données et des utilisateurs, englobant une diversité d'applications et de technologies. Cette technologie se caractérise par le déploiement d'applications au plus près des données et des utilisateurs, englobant une diversité d'applications et de technologies.
@ -28,7 +28,7 @@ Malgré ces défis, les avantages tels que l'amélioration de la rapidité de pr
Red Hat a introduit **Red Hat Device Edge**, une solution caractérisée par sa modularité, sa flexibilité et sa cohérence. Red Hat a introduit **Red Hat Device Edge**, une solution caractérisée par sa modularité, sa flexibilité et sa cohérence.
Cette solution comprend **Red Hat Enterprise Linux**, optimisé pour l'*Edge Computing*, **Microshift** — une version allégée de Kubernetes adaptée à cette technologie — et **Ansible Automation Platform** pour une gestion simplifiée à grande échelle. Cette solution comprend **Red Hat Enterprise Linux**, optimisé pour l'*Edge Computing*, **Microshift** — une version allégée de Kubernetes adaptée à cette technologie — et **Ansible Automation Platform** pour une gestion simplifiée à grande échelle.
{{< attachedFigure src="redhat-device-edge.png" >}} ![](redhat-device-edge.png)
**Red Hat Device Edge** a été conçu pour offrir une flexibilité en termes de configuration, permettant aux utilisateurs de choisir entre différentes combinaisons de systèmes d'exploitation, d'orchestration de conteneurs et d'automatisation pour optimiser les coûts. **Red Hat Device Edge** a été conçu pour offrir une flexibilité en termes de configuration, permettant aux utilisateurs de choisir entre différentes combinaisons de systèmes d'exploitation, d'orchestration de conteneurs et d'automatisation pour optimiser les coûts.

9
content/french/speaking/red-hat-open-tour-lyon-2025/index.md

@ -21,14 +21,15 @@ opensource:
Le 20 Mai 2025, j'ai participé à l'événement [Red Hat Open Tour](https://events.redhat.com/profile/form/index.cfm?PKformID=0x1406172abcd&extIdCarryOver=true&sc_cid=701f2000001Css5AAC) durant lequel j'ai animé, avec mes collègues Anisse et Mourad, un atelier de travaux pratiques combinant **Edge Computing** et **Intelligence Artificielle**. Le 20 Mai 2025, j'ai participé à l'événement [Red Hat Open Tour](https://events.redhat.com/profile/form/index.cfm?PKformID=0x1406172abcd&extIdCarryOver=true&sc_cid=701f2000001Css5AAC) durant lequel j'ai animé, avec mes collègues Anisse et Mourad, un atelier de travaux pratiques combinant **Edge Computing** et **Intelligence Artificielle**.
[L'énoncé de l'atelier](https://open-tour-2025.netlify.app/fr/) est basé sur ce que nous avions présenté lors du {{< internalLink path="/speaking/red-hat-open-tour-toulouse-2024/index.md" >}} : grâce un modèle d'intelligence artificielle conçu dans **OpenShift AI** et déployé sur un **Nvidia Jetson Orin** faisant tourner **Red Hat Device Edge**, le train reconnait les panneaux de signalisation et s'arrête tout seul lorsque la signalisation le lui impose ! [L'énoncé de l'atelier](https://open-tour-2025.netlify.app/fr/) est basé sur ce que nous avions présenté lors du [](/speaking/red-hat-open-tour-toulouse-2024/index.md) : grâce un modèle d'intelligence artificielle conçu dans **OpenShift AI** et déployé sur un **Nvidia Jetson Orin** faisant tourner **Red Hat Device Edge**, le train reconnait les panneaux de signalisation et s'arrête tout seul lorsque la signalisation le lui impose !
## Photos ## Photos
{{< attachedFigure src="participants-1.jpeg" >}} ![](participants-1.jpeg)
{{< attachedFigure src="participants-2.jpeg" >}}
![](participants-2.jpeg)
## Ressources ## Ressources
- [Énoncé de l'atelier](https://open-tour-2025.netlify.app/fr/) - [Énoncé de l'atelier](https://open-tour-2025.netlify.app/fr/)
- [Supports de présentation]({{< attachedFileLink src="slides.pdf" >}}) - [Supports de présentation](slides.pdf)

6
content/french/speaking/red-hat-open-tour-toulouse-2024/index.md

@ -21,9 +21,9 @@ opensource:
Le 28 Novembre 2024, j'ai participé à l'événement [Red Hat Open Tour](https://events.redhat.com/profile/form/index.cfm?PKformID=0x1275737abcd) durant lequel j'ai animé, avec mes collègues Adrien et Mourad, un atelier de travaux pratiques combinant **Edge Computing** et **Intelligence Artificielle**. Le 28 Novembre 2024, j'ai participé à l'événement [Red Hat Open Tour](https://events.redhat.com/profile/form/index.cfm?PKformID=0x1275737abcd) durant lequel j'ai animé, avec mes collègues Adrien et Mourad, un atelier de travaux pratiques combinant **Edge Computing** et **Intelligence Artificielle**.
{{< attachedFigure src="mas-tolosa.jpeg" title="L'événement s'est déroulé au Mas Tolosa, à proximité de Toulouse." >}} ![L'événement s'est déroulé au Mas Tolosa, à proximité de Toulouse.](mas-tolosa.jpeg "L'événement s'est déroulé au Mas Tolosa, à proximité de Toulouse.")
[L'énoncé de l'atelier](https://open-tour-2024.netlify.app/fr/) est basé sur ce que nous avions présenté lors du {{< internalLink path="/speaking/riviera-dev-2024/index.md" >}} : le scénario du 7ème opus de la saga **Mission Impossible: Dead Reckoning**... avec un train Lego ! [L'énoncé de l'atelier](https://open-tour-2024.netlify.app/fr/) est basé sur ce que nous avions présenté lors du [](/speaking/riviera-dev-2024/index.md) : le scénario du 7ème opus de la saga **Mission Impossible: Dead Reckoning**... avec un train Lego !
**Ethan Hunt** a besoin d'aide pour arrêter le train. **Ethan Hunt** a besoin d'aide pour arrêter le train.
Grâce un modèle d'intelligence artificielle conçu dans **OpenShift AI** et déployé sur un **Nvidia Jetson Orin** faisant tourner **Red Hat Device Edge**, le train reconnait les panneaux de signalisation et s'arrête tout seul ! Grâce un modèle d'intelligence artificielle conçu dans **OpenShift AI** et déployé sur un **Nvidia Jetson Orin** faisant tourner **Red Hat Device Edge**, le train reconnait les panneaux de signalisation et s'arrête tout seul !
@ -41,7 +41,7 @@ L’accent a été mis sur l’utilisation des fonctionnalités d’**OpenShift
Cette approche a permis de démystifier les étapes clés d'un projet d'Intelligence Artificielle. Cette approche a permis de démystifier les étapes clés d'un projet d'Intelligence Artificielle.
{{< attachedFigure src="participants.jpeg" title="Les participants de cet atelier de travaux pratiques ont développé, durant 3 heures, le pilote automatique du train Lego." >}} ![Les participants de cet atelier de travaux pratiques ont développé, durant 3 heures, le pilote automatique du train Lego.](participants.jpeg "Les participants de cet atelier de travaux pratiques ont développé, durant 3 heures, le pilote automatique du train Lego.")
Pour rendre l’exercice accessible et réduire les prérequis matériels, la suite de l’atelier s’est appuyé sur une approche ingénieuse. Pour rendre l’exercice accessible et réduire les prérequis matériels, la suite de l’atelier s’est appuyé sur une approche ingénieuse.
Plutôt que d’exiger un set Lego complet plus une carte Jetson Orin Nano par participant, tout a été conteneurisé et bouchonné : Plutôt que d’exiger un set Lego complet plus une carte Jetson Orin Nano par participant, tout a été conteneurisé et bouchonné :

3
content/french/speaking/red-hat-openshift-user-group-3/index.md

@ -20,4 +20,5 @@ Durant la session, de nombreuses questions ont été abordées :
- Quel modèles organisationnels au niveau du dépôt Git ? un dépot par environnement ? une branche par environnement ? quelles droits d'accès ? - Quel modèles organisationnels au niveau du dépôt Git ? un dépot par environnement ? une branche par environnement ? quelles droits d'accès ?
- Comment sont gérées les approbations pour le déploiement en production ? - Comment sont gérées les approbations pour le déploiement en production ?
{{< attachedFigure src="photo-participants.jpeg" >}} ![](photo-participants.jpeg)

3
content/french/speaking/red-hat-openshift-user-group-4/index.md

@ -12,4 +12,5 @@ opensource:
Le 5 Décembre 2023, j'ai participé à l'événement [OpenShift User Group #4](https://events.redhat.com/profile/form/index.cfm?PKformID=0x951578abcd) durant lequel j'ai animé les groupes de travail *"Observabilité au sein d’OpenShift"* et *"Cluster as a Service"*. Le 5 Décembre 2023, j'ai participé à l'événement [OpenShift User Group #4](https://events.redhat.com/profile/form/index.cfm?PKformID=0x951578abcd) durant lequel j'ai animé les groupes de travail *"Observabilité au sein d’OpenShift"* et *"Cluster as a Service"*.
{{< attachedFigure src="photo-participants.jpeg" >}} ![](photo-participants.jpeg)

7
content/french/speaking/red-hat-openshift-user-group-5/index.md

@ -13,7 +13,7 @@ opensource:
Le 14 Mai 2024, j'ai participé à l'événement [OpenShift User Group #5](https://events.redhat.com/profile/form/index.cfm?PKformID=0x1014468abcd) [💾](https://web.archive.org/web/20240522122155/https://events.redhat.com/profile/form/index.cfm?PKformID=0x1014468abcd) durant lequel j'ai animé les groupes de travail *"Sécurité des Conteneurs"* et *"Gestion multi-cluster"*. Le 14 Mai 2024, j'ai participé à l'événement [OpenShift User Group #5](https://events.redhat.com/profile/form/index.cfm?PKformID=0x1014468abcd) [💾](https://web.archive.org/web/20240522122155/https://events.redhat.com/profile/form/index.cfm?PKformID=0x1014468abcd) durant lequel j'ai animé les groupes de travail *"Sécurité des Conteneurs"* et *"Gestion multi-cluster"*.
{{< attachedFigure src="atelier-gestion-multi-cluster.jpeg" >}} ![](atelier-gestion-multi-cluster.jpeg)
Les deux ateliers se sont déroulés de manière collaborative : Les deux ateliers se sont déroulés de manière collaborative :
@ -23,6 +23,7 @@ Les deux ateliers se sont déroulés de manière collaborative :
Dans les graphes ci-dessous, les thèmes couverts sont en bleu et les thèmes restant à couvrir sont en gris. Dans les graphes ci-dessous, les thèmes couverts sont en bleu et les thèmes restant à couvrir sont en gris.
{{< attachedFigure src="atelier-gestion-multi-cluster.svg" title="Sujets abordés lors de l'atelier \"Gestion multi-cluster\"." >}} ![Sujets abordés lors de l'atelier "Gestion multi-cluster".](atelier-gestion-multi-cluster.svg "Sujets abordés lors de l'atelier \"Gestion multi-cluster\".")
![Sujets abordés lors de l'atelier "Sécurité des conteneurs".](atelier-securite-conteneur.svg "Sujets abordés lors de l'atelier \"Sécurité des conteneurs\".")
{{< attachedFigure src="atelier-securite-conteneur.svg" title="Sujets abordés lors de l'atelier \"Sécurité des conteneurs\"." >}}

10
content/french/speaking/red-hat-summit-connect-france-2023/index.md

@ -16,7 +16,7 @@ Il y avait plus de 600 personnes ce jour là.
Retrouvez ci-dessous les principaux éléments de ma *Keynote* ! Retrouvez ci-dessous les principaux éléments de ma *Keynote* !
{{< attachedFigure src="RedHat.HL-56.jpeg" >}} ![](RedHat.HL-56.jpeg)
Le DevOps est un sujet absolument **passionnant**, pour lequel les entreprises en attendent de **produire plus de valeur, avec toujours moins de coûts**. Le DevOps est un sujet absolument **passionnant**, pour lequel les entreprises en attendent de **produire plus de valeur, avec toujours moins de coûts**.
Mais rien de tout cela n'est vraiment **nouveau**. Mais rien de tout cela n'est vraiment **nouveau**.
@ -56,7 +56,7 @@ Developer Hub vous offre une plateforme développeur Open Source, avec la stabil
Developer Hub **est basé sur Red Hat OpenShift** et tire parti des compétences que vous avez déjà sur OpenShift. Developer Hub **est basé sur Red Hat OpenShift** et tire parti des compétences que vous avez déjà sur OpenShift.
Et c’est probablement l’information la plus importante de la journée : **si vous avez déjà OpenShift, Red Hat Developer Hub vous tend les bras pour améliorer la productivité de vos développeurs.** Et c’est probablement l’information la plus importante de la journée : **si vous avez déjà OpenShift, Red Hat Developer Hub vous tend les bras pour améliorer la productivité de vos développeurs.**
{{< attachedFigure src="RedHat.HL-61.jpeg" >}} ![](RedHat.HL-61.jpeg)
Et je ne peux pas terminer cette partie sur le développement applicatif sans parler du sujet à la mode : **l’intelligence artificielle**. Et je ne peux pas terminer cette partie sur le développement applicatif sans parler du sujet à la mode : **l’intelligence artificielle**.
L’IA est utilisée pour résoudre des problèmes compliqués, qui s’il devaient être implémentés de manière traditionnelle, ne le serait pas ou alors trop coûteux pour être rentable. L’IA est utilisée pour résoudre des problèmes compliqués, qui s’il devaient être implémentés de manière traditionnelle, ne le serait pas ou alors trop coûteux pour être rentable.
@ -75,7 +75,7 @@ OpenShift AI vous permet de créer, entraîner et mettre à disposition vos mod
Et après vous avoir parlé de DevOps et d’IA, abordons le thème de la sécurité informatique. Et après vous avoir parlé de DevOps et d’IA, abordons le thème de la sécurité informatique.
{{< attachedFigure src="RedHat.HL-65.jpeg" >}} ![](RedHat.HL-65.jpeg)
La sécurité informatique, c’est un peu le parent pauvre de notre industrie: **on y pense quand il est trop tard**, quand le problème est là. La sécurité informatique, c’est un peu le parent pauvre de notre industrie: **on y pense quand il est trop tard**, quand le problème est là.
Cette situation est exacerbée par la **complexité** et l’étendue des architectures logicielles. Cette situation est exacerbée par la **complexité** et l’étendue des architectures logicielles.
@ -119,8 +119,8 @@ Permettez-moi de vous présenter **Red Hat Trusted Software Supply Chain**, notr
C’est une sélections de packages, bibliothèque et dépendances spécialement sélectionnés par Red Hat. C’est une sélections de packages, bibliothèque et dépendances spécialement sélectionnés par Red Hat.
C’est également une plateforme de CI pour construire des applicatifs de manière sécurisée. C’est également une plateforme de CI pour construire des applicatifs de manière sécurisée.
{{< attachedFigure src="RedHat.HL-63.jpeg" >}} ![](RedHat.HL-63.jpeg)
[Téléchargez les slides de ma Keynote !]({{< attachedFileLink src="slides.pdf" >}}) [Téléchargez les slides de ma Keynote !](slides.pdf)
À l'année prochaine ! À l'année prochaine !

26
content/french/speaking/red-hat-summit-connect-france-2024/index.md

@ -33,7 +33,7 @@ Le tout en les plongeant dans une aventure captivante où chaque exercice impliq
Lors de cet atelier, les participants devaient développer pas moins de quatre micro-services pour construire une application de simulation de combat entre super héros et super villains. Lors de cet atelier, les participants devaient développer pas moins de quatre micro-services pour construire une application de simulation de combat entre super héros et super villains.
{{< attachedFigure src="open-code-quest-microservices.png" >}} ![](open-code-quest-microservices.png)
Les micro-services ont été développés en Quarkus, le framework Java natif pour le cloud, en démontrant comment il peut transformer le développement d’applications en alliant rapidité de développement, légèreté et performance. Les micro-services ont été développés en Quarkus, le framework Java natif pour le cloud, en démontrant comment il peut transformer le développement d’applications en alliant rapidité de développement, légèreté et performance.
En particulier, Quarkus réduit considérablement l'empreinte mémoire des applications, tout en permettant un démarrage quasi instantané. En particulier, Quarkus réduit considérablement l'empreinte mémoire des applications, tout en permettant un démarrage quasi instantané.
@ -49,7 +49,7 @@ Les participants ont pu découvrir comment ces outils permettent de renforcer la
Je vous laisse découvrir la liste complète de l'outillage utilisé dans l'atelier **Open Code Quest** : Je vous laisse découvrir la liste complète de l'outillage utilisé dans l'atelier **Open Code Quest** :
{{< attachedFigure src="open-code-quest-namespaces.png" >}} ![](open-code-quest-namespaces.png)
Avant et pendant l'**Open Code Quest**, la gestion de la **plateforme** a joué un rôle clé dans la réussite de l'événement. Avant et pendant l'**Open Code Quest**, la gestion de la **plateforme** a joué un rôle clé dans la réussite de l'événement.
En tant que membre organisateur, j’ai eu la responsabilité, avec [Sébastien Lallemand](https://sebastienlallemand.net/), de préparer, dimensionner, installer et configurer les huit clusters OpenShift nécessaires au bon déroulement des ateliers. En tant que membre organisateur, j’ai eu la responsabilité, avec [Sébastien Lallemand](https://sebastienlallemand.net/), de préparer, dimensionner, installer et configurer les huit clusters OpenShift nécessaires au bon déroulement des ateliers.
@ -58,7 +58,7 @@ Cette phase cruciale de préparation a permis de garantir une infrastructure sta
Pendant l’événement, mon rôle de SRE (Site Reliability Engineer) consistait à surveiller de près les métriques critiques, telles que l'utilisation des ressources, afin d'assurer une expérience fluide et optimale pour tous les participants. Pendant l’événement, mon rôle de SRE (Site Reliability Engineer) consistait à surveiller de près les métriques critiques, telles que l'utilisation des ressources, afin d'assurer une expérience fluide et optimale pour tous les participants.
Grâce à cette surveillance proactive nous avons pu offir une disponibilité constante des environnements et ainsi faciliter le bon déroulement de l'atelier. Grâce à cette surveillance proactive nous avons pu offir une disponibilité constante des environnements et ainsi faciliter le bon déroulement de l'atelier.
{{< attachedFigure src="open-code-quest-clusters.png" >}} ![](open-code-quest-clusters.png)
Un autre défi que j'ai relevé pour l'Open Code Quest a été la création d'un **Leaderboard** destiné à favoriser l'émulation entre les participants. Un autre défi que j'ai relevé pour l'Open Code Quest a été la création d'un **Leaderboard** destiné à favoriser l'émulation entre les participants.
Ce projet m'a demandé de sortir des sentiers battus, car j'ai dû utiliser des outils tels que **Prometheus** et **Grafana** pour une tâche à laquelle ils ne sont pas destinés : départager les participants par ordre d'arrivée. Ce projet m'a demandé de sortir des sentiers battus, car j'ai dû utiliser des outils tels que **Prometheus** et **Grafana** pour une tâche à laquelle ils ne sont pas destinés : départager les participants par ordre d'arrivée.
@ -79,23 +79,23 @@ Comment j'ai calibré les bonus et accélérateurs pour favoriser la compétitio
Tout est expliqué dans ces deux articles : Tout est expliqué dans ces deux articles :
1. {{< internalLink path="/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md" >}} 1. [](/blog/behind-the-scenes-at-open-code-quest-how-i-designed-leaderboard/index.md)
2. {{< internalLink path="/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md" >}} 2. [](/blog/behind-the-scenes-at-open-code-quest-how-i-implemented-leaderboard-with-acm/index.md)
## Démo "Mission Impossible" : Lego, AI & Edge Computing ## Démo "Mission Impossible" : Lego, AI & Edge Computing
Une partie de la journée, j'étais sur le stand RHEL, accompagné de [Adrien](https://www.linkedin.com/in/adrien-legros-78674a133/), [Mourad](https://www.linkedin.com/in/mourad-ouachani-0734218/) et [Pauline](https://www.linkedin.com/in/trg-pauline/) pour installer la démo "Mission Impossible" et répondre aux questions du public. Une partie de la journée, j'étais sur le stand RHEL, accompagné de [Adrien](https://www.linkedin.com/in/adrien-legros-78674a133/), [Mourad](https://www.linkedin.com/in/mourad-ouachani-0734218/) et [Pauline](https://www.linkedin.com/in/trg-pauline/) pour installer la démo "Mission Impossible" et répondre aux questions du public.
Cette démo, nous l'avons conçue pour l'événement {{< internalLink path="/speaking/platform-day-2024/index.md" >}} sur le thème du dernier opus du film **Mission Impossible: Dead Reckoning**. Cette démo, nous l'avons conçue pour l'événement [](/speaking/platform-day-2024/index.md) sur le thème du dernier opus du film **Mission Impossible: Dead Reckoning**.
Dans cette démo, **Ethan Hunt** a besoin d'aide pour arrêter le train **Lego City #60337** avant qu'il ne soit trop tard ! Dans cette démo, **Ethan Hunt** a besoin d'aide pour arrêter le train **Lego City #60337** avant qu'il ne soit trop tard !
Rien de moins que le sort de l'humanité est en jeu ! Rien de moins que le sort de l'humanité est en jeu !
{{< attachedFigure src="mission-impossible-plot.png" >}} ![](mission-impossible-plot.png)
Le scénario nécessite que **Ethan Hunt** monte à bord du train pour y connecter une carte **Nvidia Jetson Orin Nano** au réseau informatique du train et y déploie une IA qui reconnaitra les panneaux de signalisation et arrêtera le train à temps avant qu'il ne déraille ! Le scénario nécessite que **Ethan Hunt** monte à bord du train pour y connecter une carte **Nvidia Jetson Orin Nano** au réseau informatique du train et y déploie une IA qui reconnaitra les panneaux de signalisation et arrêtera le train à temps avant qu'il ne déraille !
Une console permettra d'avoir une vue déportée de la caméra de vidéo surveillance du train, avec les résultats de l'inférence du modèle d'IA incrustés. Une console permettra d'avoir une vue déportée de la caméra de vidéo surveillance du train, avec les résultats de l'inférence du modèle d'IA incrustés.
{{< attachedFigure src="mission-impossible-scenario.png" >}} ![](mission-impossible-scenario.png)
Pour mettre en oeuvre cette démo, nous avons équipé le train **Lego** d'une carte **Nvidia Jetson Orin Nano**, d'une webcam et d'une batterie portable. Pour mettre en oeuvre cette démo, nous avons équipé le train **Lego** d'une carte **Nvidia Jetson Orin Nano**, d'une webcam et d'une batterie portable.
La carte Nvidia Jetson Orin est un System On Chip (SoC), elle comprend tout le matériel dont **Ethan Hunt** a besoin pour sa mission : CPU, RAM, stockage... La carte Nvidia Jetson Orin est un System On Chip (SoC), elle comprend tout le matériel dont **Ethan Hunt** a besoin pour sa mission : CPU, RAM, stockage...
@ -103,7 +103,7 @@ Ainsi qu'un un GPU pour accélérer les calculs !
Le Jetson reçoit le flux vidéo de la caméra embarquée et transmet les ordres au Hub **Lego** via le protocole **Bluetooth Low Energy**. Le Jetson reçoit le flux vidéo de la caméra embarquée et transmet les ordres au Hub **Lego** via le protocole **Bluetooth Low Energy**.
Il est alimenté via une batterie portable pour la durée de la mission. Il est alimenté via une batterie portable pour la durée de la mission.
{{< attachedFigure src="rhel-booth-mission-impossible-demo.jpeg" >}} ![](rhel-booth-mission-impossible-demo.jpeg)
Nous sommes dans un contexte de Edge Computing. Nous sommes dans un contexte de Edge Computing.
Sur le Jetson, nous avons installé **Red Hat Device Edge**. Sur le Jetson, nous avons installé **Red Hat Device Edge**.
@ -118,7 +118,7 @@ Le flux vidéo est relayé depuis le Jetson au travers d’un **broker Kafka** !
Il faut ajouter à cela des pipelines MLops pour entraîner le modèle d’IA. Il faut ajouter à cela des pipelines MLops pour entraîner le modèle d’IA.
Et enfin des pipelines CI/CD pour construire les images de conteneur de nos micro-services pour les architectures x86 et ARM. Et enfin des pipelines CI/CD pour construire les images de conteneur de nos micro-services pour les architectures x86 et ARM.
{{< attachedFigure src="mission-impossible-hardware-architecture.png" >}} ![](mission-impossible-hardware-architecture.png)
Pour permettre à **Ethan Hunt** de mener à bien sa mission, il a fallu garantir la transmission de la donnée de bout en bout. Pour permettre à **Ethan Hunt** de mener à bien sa mission, il a fallu garantir la transmission de la donnée de bout en bout.
Pour cela, nous avons implémenté cinq services qui communiquent via un système d’envoi de messages asynchrone (**MQTT**). Pour cela, nous avons implémenté cinq services qui communiquent via un système d’envoi de messages asynchrone (**MQTT**).
@ -128,7 +128,7 @@ Chaque image est redimensionnée en 600x400 pixels et encapsulée dans un évén
Cet événement est transmis au modèle d'IA qui l’enrichit avec le résultat de la prédiction. Cet événement est transmis au modèle d'IA qui l’enrichit avec le résultat de la prédiction.
Ce dernier est transmis à un service de transformation qui a pour rôle d'extraire l'action du train, la transmettre au contrôleur de train pour ralentir ou stopper le train et en parallèle envoyer l'événement au service de streaming (**Kafka**) déployé sur un Openshift distant, qui affiche en temps réel, les images et la prédiction. Ce dernier est transmis à un service de transformation qui a pour rôle d'extraire l'action du train, la transmettre au contrôleur de train pour ralentir ou stopper le train et en parallèle envoyer l'événement au service de streaming (**Kafka**) déployé sur un Openshift distant, qui affiche en temps réel, les images et la prédiction.
{{< attachedFigure src="mission-impossible-software-architecture.png" >}} ![](mission-impossible-software-architecture.png)
Et enfin, il nous a fallu construire un modèle d’intelligence artificielle. Et enfin, il nous a fallu construire un modèle d’intelligence artificielle.
Pour cela, nous avons suivi les bonnes pratiques pour gérer le cycle de vie du modèle, c’est ce qu’on appelle le **MLOps** : Pour cela, nous avons suivi les bonnes pratiques pour gérer le cycle de vie du modèle, c’est ce qu’on appelle le **MLOps** :
@ -141,9 +141,9 @@ Pour cela, nous avons suivi les bonnes pratiques pour gérer le cycle de vie du
- **Mesurer les performances et ré-entraîner** : En observer le comportement du modèle, nous avons pu mesurer la qualité des prédictions et constater que tous les panneaux **Lego** n'était pas bien reconnus. - **Mesurer les performances et ré-entraîner** : En observer le comportement du modèle, nous avons pu mesurer la qualité des prédictions et constater que tous les panneaux **Lego** n'était pas bien reconnus.
Nous avons pris la décision de réentrainer le modèle en l’affinant avec un jeu de données enrichi. Nous avons pris la décision de réentrainer le modèle en l’affinant avec un jeu de données enrichi.
{{< attachedFigure src="mission-impossible-ai.png" >}} ![](mission-impossible-ai.png)
Si vous n'avez pas pu venir nous voir sur le stand, je vous propose une session de rattrapage dans la vidéo ci-dessous (capturée lors du {{< internalLink path="/speaking/platform-day-2024/index.md" >}}). Si vous n'avez pas pu venir nous voir sur le stand, je vous propose une session de rattrapage dans la vidéo ci-dessous (capturée lors du [](/speaking/platform-day-2024/index.md)).
On y voit le train s'arrêter lorsqu'il détecte le panneau de signalisation correspondant. On y voit le train s'arrêter lorsqu'il détecte le panneau de signalisation correspondant.
{{< embeddedVideo src="mission-impossible-demo.mp4" autoplay="true" loop="true" muted="true" width="1920" height="1080" >}} {{< embeddedVideo src="mission-impossible-demo.mp4" autoplay="true" loop="true" muted="true" width="1920" height="1080" >}}

2
content/french/speaking/redhat-summit-connect-2021/index.md

@ -11,5 +11,5 @@ resources:
J'ai eu l'occasion de participer au salon [Red Hat Summit Connect 2021](https://www.redhat.com/fr/summit/connect/emea/paris) pour co-présenter la solution [Red Hat Advanced Cluster Security](https://www.redhat.com/fr/technologies/management/advanced-cluster-management) avec mon collègue [François Duthilleul](https://www.linkedin.com/posts/fduthilleul_redhat-summitconnect-paris-activity-6865998968166354944-QPlI). J'ai eu l'occasion de participer au salon [Red Hat Summit Connect 2021](https://www.redhat.com/fr/summit/connect/emea/paris) pour co-présenter la solution [Red Hat Advanced Cluster Security](https://www.redhat.com/fr/technologies/management/advanced-cluster-management) avec mon collègue [François Duthilleul](https://www.linkedin.com/posts/fduthilleul_redhat-summitconnect-paris-activity-6865998968166354944-QPlI).
{{< screenshotOf src="tweet-YadaYac.png" href="https://twitter.com/YadaYac/status/1461043727951093762" >}} [![Tweet de @YadaYac](tweet-YadaYac.png)](https://twitter.com/YadaYac/status/1461043727951093762)

10
content/french/speaking/riviera-dev-2024/index.md

@ -22,7 +22,7 @@ L'énoncé de l'atelier est basé sur le scénario du 7ème opus de la saga **Mi
**Ethan Hunt** a besoin d'aide pour arrêter le train. **Ethan Hunt** a besoin d'aide pour arrêter le train.
Grâce un modèle d'intelligence artificielle conçu dans **OpenShift AI** et déployé sur un **Nvidia Jetson Orin** faisant tourner **Red Hat Device Edge**, le train reconnait les panneaux de signalisation et s'arrête tout seul ! Grâce un modèle d'intelligence artificielle conçu dans **OpenShift AI** et déployé sur un **Nvidia Jetson Orin** faisant tourner **Red Hat Device Edge**, le train reconnait les panneaux de signalisation et s'arrête tout seul !
{{< attachedFigure src="train-lego-table-1.webp" title="Les participants ont dû résoudre une série d'exercice avec un train Lego sur le thème de Mission Impossible." >}} ![Les participants ont dû résoudre une série d'exercice avec un train Lego sur le thème de Mission Impossible.](train-lego-table-1.webp "Les participants ont dû résoudre une série d'exercice avec un train Lego sur le thème de Mission Impossible.")
[L'énoncé de l'atelier](https://rivieradev2024-crazytrain.netlify.app/) est composé de trois parties : [L'énoncé de l'atelier](https://rivieradev2024-crazytrain.netlify.app/) est composé de trois parties :
@ -46,7 +46,7 @@ Pour le participants, ça avait l'effet de bord positif de ne requérir aucun pr
Des fichiers vidéos sont stockés dans Git et sont utilisés à la place de la webcam. Des fichiers vidéos sont stockés dans Git et sont utilisés à la place de la webcam.
La communication avec le Hub Lego est remplaçée par l'affichage des ordres qui aurait été envoyés si le Hub était présent. La communication avec le Hub Lego est remplaçée par l'affichage des ordres qui aurait été envoyés si le Hub était présent.
{{< attachedFigure src="train-lego-table-2.webp" title="Tous les participants sont concentrés sur la réalisation des exercices." >}} ![Tous les participants sont concentrés sur la réalisation des exercices.](train-lego-table-2.webp "Tous les participants sont concentrés sur la réalisation des exercices.")
Enfin, la partie DevOps était composé de deux étapes : Enfin, la partie DevOps était composé de deux étapes :
@ -57,7 +57,7 @@ Si l'atelier s'est globalement bien passé, nous avons eu la surprise de constat
Le symptôme que nous avions était une WebSocket connectée mais vide. Le symptôme que nous avions était une WebSocket connectée mais vide.
Après investigation, il semblerait que le problème soit lié à un logiciel de sécurité installé sur les machines des participants. Après investigation, il semblerait que le problème soit lié à un logiciel de sécurité installé sur les machines des participants.
{{< attachedFigure src="aide-ponctuelle.webp" title="Le problème de Web Socket a nécessité de la patience pour les participants et les intervenants Red Hat." >}} ![Le problème de Web Socket a nécessité de la patience pour les participants et les intervenants Red Hat.](aide-ponctuelle.webp "Le problème de Web Socket a nécessité de la patience pour les participants et les intervenants Red Hat.")
L'atelier s'est terminé avec un Quizz de connaissances et le vainqueur s'est vu remettre un [set Lego #60337](https://www.lego.com/fr-fr/product/express-passenger-train-60337) (le même train que celui de notre démo). L'atelier s'est terminé avec un Quizz de connaissances et le vainqueur s'est vu remettre un [set Lego #60337](https://www.lego.com/fr-fr/product/express-passenger-train-60337) (le même train que celui de notre démo).
@ -65,7 +65,7 @@ Participer à cet atelier lors du Riviera Dev 2024 a été une expérience enric
Non seulement nous avons pu pratiquer sur deux sujets du moment : Edge Computing et Intelligence Artificielle, mais nous avons également offert aux participants une approche ludique à travers le prisme de la saga Mission Impossible. Non seulement nous avons pu pratiquer sur deux sujets du moment : Edge Computing et Intelligence Artificielle, mais nous avons également offert aux participants une approche ludique à travers le prisme de la saga Mission Impossible.
Les défis rencontrés, notamment les soucis de lecture du flux vidéo, ont ajouté un peu de piment et d'émulation collective à l'exercice. Les défis rencontrés, notamment les soucis de lecture du flux vidéo, ont ajouté un peu de piment et d'émulation collective à l'exercice.
{{< attachedFigure src="groupe.webp" title="Le vainqueur du Quizz était visiblement content de l'atelier. 😅" >}} ![Le vainqueur du Quizz était visiblement content de l'atelier. 😅](groupe.webp "Le vainqueur du Quizz était visiblement content de l'atelier. 😅")
Au-delà des compétences techniques acquises, l'enthousiasme et la bonne humeur des participants ont fait de cet atelier un moment de partage et d'apprentissage. Au-delà des compétences techniques acquises, l'enthousiasme et la bonne humeur des participants ont fait de cet atelier un moment de partage et d'apprentissage.
Le quizz final a apporté une touche de compétition amicale et le prix (un set [Lego City #60337](https://www.lego.com/fr-fr/product/express-passenger-train-60337)) décerné au vainqueur a visiblement rendu ce dernier très heureux ! Le quizz final a apporté une touche de compétition amicale et le prix (un set [Lego City #60337](https://www.lego.com/fr-fr/product/express-passenger-train-60337)) décerné au vainqueur a visiblement rendu ce dernier très heureux !
@ -75,6 +75,6 @@ Merci à tous les participants pour leur engagement et leur curiosité, et à l'
En dehors de l'atelier, il y a eu également une forte affluence sur le stand Red Hat où nous avions installé le train et ses rails. En dehors de l'atelier, il y a eu également une forte affluence sur le stand Red Hat où nous avions installé le train et ses rails.
Ce fût l'occasion d'échanger sur les projets Edge et IA en cours ainsi que sur les technologies utilisées. Ce fût l'occasion d'échanger sur les projets Edge et IA en cours ainsi que sur les technologies utilisées.
{{< attachedFigure src="stand-redhat-1.webp" title="Le train était installé sur le stand Red Hat et a provoqué de nombreuses discussions." >}} ![Le train était installé sur le stand Red Hat et a provoqué de nombreuses discussions.](stand-redhat-1.webp "Le train était installé sur le stand Red Hat et a provoqué de nombreuses discussions.")
A l'année prochaine ! A l'année prochaine !

2
content/french/speaking/worldline-tech-forum-explore-france-2024/index.md

@ -12,7 +12,7 @@ topics:
[Le 18 juin 2024](https://www.linkedin.com/posts/red-hat_worldline-techatworldline-worldlinetex-activity-7209161785776451584-7-Hx?utm_source=share&utm_medium=member_desktop), Seclin a accueilli un événement technologique majeur : le [Worldline Tech Forum eXplore France 2024](https://www.linkedin.com/posts/worldlineglobal_worldline-tech-forum-explore-france-2024-activity-7218612563469578240-fTZf/). [Le 18 juin 2024](https://www.linkedin.com/posts/red-hat_worldline-techatworldline-worldlinetex-activity-7209161785776451584-7-Hx?utm_source=share&utm_medium=member_desktop), Seclin a accueilli un événement technologique majeur : le [Worldline Tech Forum eXplore France 2024](https://www.linkedin.com/posts/worldlineglobal_worldline-tech-forum-explore-france-2024-activity-7218612563469578240-fTZf/).
Cet événement a rassemblé des employés Worldline et des partenaires technologiques (dont Red Hat), tous animés par une même volonté de partager les dernières innovations et leurs connaissances. Cet événement a rassemblé des employés Worldline et des partenaires technologiques (dont Red Hat), tous animés par une même volonté de partager les dernières innovations et leurs connaissances.
Parmi les nombreuses animations proposées lors de cette journée, notre stand Red Hat a fait sensation, notamment grâce à une démonstration originale qui a attiré de nombreux visiteurs : le désormais bien connu train Lego *Mission Impossible*, que nous avons présenté lors du {{< internalLink path="/speaking/platform-day-2024/index.md" >}}. Parmi les nombreuses animations proposées lors de cette journée, notre stand Red Hat a fait sensation, notamment grâce à une démonstration originale qui a attiré de nombreux visiteurs : le désormais bien connu train Lego *Mission Impossible*, que nous avons présenté lors du [](/speaking/platform-day-2024/index.md).
## Une expérience immersive et ludique avec le train Lego ## Une expérience immersive et ludique avec le train Lego

1
content/french/topics/_index.md

@ -1,5 +1,6 @@
--- ---
title: "Expertise" title: "Expertise"
description: "Les sujets abordés sur ce site : API Management, conteneurs, sécurité, tests de performance, etc."
--- ---
Au cours de mes différentes prestations, je suis amené à travailler sur des sujets variés pour lesquels j'ai écrit un ou plusieurs articles. Au cours de mes différentes prestations, je suis amené à travailler sur des sujets variés pour lesquels j'ai écrit un ou plusieurs articles.

1
content/french/writing/_index.md

@ -1,5 +1,6 @@
--- ---
title: "Publications" title: "Publications"
description: "Articles publiés par Nicolas Massé en tant qu'auteur invité sur d'autres sites web et dans la presse spécialisée."
date: 2019-11-07T00:00:00+02:00 date: 2019-11-07T00:00:00+02:00
draft: false draft: false
--- ---

24
layouts/_markup/render-codeblock.html

@ -0,0 +1,24 @@
{{- /*
-- Renders the fenced code blocks, optionally prefixed by a file name or a
-- title:
--
-- ```yaml {filename="config.yaml" hl_lines="3-5"}
-- ```sh {title="CoreOS"}
*/ -}}
{{- $result := transform.HighlightCodeBlock . -}}
{{- $label := "" -}}
{{- with .Attributes.filename -}}
{{- $label = printf (i18n "file") . -}}
{{- else -}}
{{- with .Attributes.title -}}{{- $label = . -}}{{- end -}}
{{- end -}}
{{- /* The filename and title attributes must not be rendered on the wrapper */ -}}
{{- $highlighted := replaceRE `^<div class="highlight"[^>]*>` `<div class="highlight">` (string $result.Wrapped) 1 | safeHTML -}}
{{- if $label -}}
<div class="highlightedFile">
<div class="filename">{{ $label }}</div>
{{- $highlighted -}}
</div>
{{- else -}}
{{- $highlighted -}}
{{- end -}}

88
layouts/_markup/render-image.html

@ -0,0 +1,88 @@
{{- /*
-- Renders the Markdown images.
--
-- The image is resolved to a resource of the current page, or to a global
-- resource (assets, static/images). A standalone image is rendered as a
-- figure, with its title as caption (Markdown allowed):
--
-- ![Alternative text](image.png "Caption")
--
-- Raster images (JPEG, PNG, WebP) are converted to WebP in several widths
-- (srcset), so that the browser downloads the most appropriate one.
-- GIF (possibly animated) and SVG images are served as-is.
--
-- All images get their dimensions (to avoid layout shifts) and are lazy
-- loaded, except the first one of the page.
*/ -}}
{{- $u := urls.Parse .Destination -}}
{{- $attrs := dict "src" .Destination "alt" .PlainText -}}
{{- $image := false -}}
{{- if not $u.IsAbs -}}
{{- with or (.Page.Resources.Get $u.Path) (resources.Get (strings.TrimPrefix "/" $u.Path)) -}}
{{- $image = . -}}
{{- else -}}
{{- if not (strings.HasPrefix $u.Path "/") -}}
{{- errorf "%s: image %q not found" .Page.String .Destination -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- with $image -}}
{{- /* Note: .RelPermalink publishes the original image, only done when it is served as-is */ -}}
{{- if in (slice "jpeg" "png" "webp") .MediaType.SubType -}}
{{- /* WebP variants, no wider than the original image */ -}}
{{- $hint := cond (eq .MediaType.SubType "png") "drawing" "photo" -}}
{{- $variants := slice -}}
{{- range $width := slice 480 800 1200 1600 -}}
{{- if lt $width $image.Width -}}
{{- $variants = $variants | append ($image.Resize (printf "%dx webp q80 %s" $width $hint)) -}}
{{- end -}}
{{- end -}}
{{- if lt (len $variants) 4 -}}
{{- /* The original width, when smaller than the largest variant */ -}}
{{- $variants = $variants | append ($image.Process (printf "webp q80 %s" $hint)) -}}
{{- end -}}
{{- $srcset := slice -}}
{{- range $variants -}}
{{- $srcset = $srcset | append (printf "%s %dw" .RelPermalink .Width) -}}
{{- end -}}
{{- $largest := index $variants (sub (len $variants) 1) -}}
{{- $attrs = merge $attrs (dict
"src" $largest.RelPermalink
"srcset" (delimit $srcset ", ")
"sizes" "(max-width: 1140px) 100vw, (max-width: 1280px) 75vw, (max-width: 1440px) 55vw, 50vw"
"width" $largest.Width
"height" $largest.Height
) -}}
{{- else if eq .MediaType.SubType "gif" -}}
{{- $attrs = merge $attrs (dict "src" .RelPermalink "width" .Width "height" .Height) -}}
{{- else -}}
{{- $attrs = merge $attrs (dict "src" .RelPermalink) -}}
{{- end -}}
{{- end -}}
{{- /* The first image of the page is often the largest visible element */ -}}
{{- if eq .Ordinal 0 -}}
{{- $attrs = merge $attrs (dict "fetchpriority" "high") -}}
{{- else -}}
{{- $attrs = merge $attrs (dict "loading" "lazy" "decoding" "async") -}}
{{- end -}}
{{- $img := "" -}}
{{- range $k, $v := $attrs -}}
{{- $img = printf `%s %s="%s"` $img $k (string $v | htmlEscape) -}}
{{- end -}}
{{- $img = printf "<img%s>" $img | safeHTML -}}
{{- if .IsBlock -}}
<figure>
{{ $img }}
{{- with .Title }}
<figcaption>
{{- . | markdownify -}}
</figcaption>
{{- end }}
</figure>
{{- else -}}
{{ $img }}
{{- end -}}

64
layouts/_markup/render-link.html

@ -0,0 +1,64 @@
{{- /*
-- Renders the Markdown links.
--
-- Internal links are resolved, in this order:
-- 1. to a resource of the current page (attached file), which is then
-- published: [the slides](slides.pdf)
-- 2. to a page of the current site, then of the other site (language).
-- A flag is displayed when the target page is in a different language
-- than the current page. When the link text is empty, the title of the
-- target page is used: [](/blog/my-article.md)
-- 3. relative links (../other-article/) are made relative to the site root,
-- so that they also work in the RSS feeds.
--
-- The output must not contain any extra whitespace since the link is
-- usually embedded in a sentence.
*/ -}}
{{- $u := urls.Parse .Destination -}}
{{- $href := .Destination -}}
{{- /*
-- An image wrapped in a link ([![alt](image.png)](URL)) is rendered as a
-- figure by the image render hook: a figure is not allowed in a link, so
-- only the image is kept.
*/ -}}
{{- $text := replaceRE `^\s*<figure>\s*(<img[^>]*>)\s*</figure>\s*$` "$1" .Text -}}
{{- $flag := "" -}}
{{- /* Links whose destination is a shortcode are left as-is (placeholder) */ -}}
{{- if and (not $u.IsAbs) $u.Path (not (strings.Contains .Destination "HAHAHUGOSHORTCODE")) -}}
{{- $fragment := cond (ne $u.Fragment "") (printf "#%s" $u.Fragment) "" -}}
{{- $currentPage := .Page -}}
{{- $currentLang := .Page.Lang -}}
{{- /* 1. Resources of the current page (attached files) */ -}}
{{- with .Page.Resources.Get $u.Path -}}
{{- $href = printf "%s%s" .RelPermalink $fragment -}}
{{- else -}}
{{- /* 2. Pages of the current site first, then of the other site */ -}}
{{- $target := $currentPage.GetPage $u.Path -}}
{{- if and (not $target) (strings.HasPrefix $u.Path "/") -}}
{{- range hugo.Sites -}}
{{- if and (not $target) (ne .Language.Lang $currentLang) -}}
{{- with .GetPage $u.Path -}}{{- $target = . -}}{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if $target -}}
{{- $href = printf "%s%s" $target.RelPermalink $fragment -}}
{{- if not $text -}}{{- $text = $target.Title | htmlEscape -}}{{- end -}}
{{- if and (eq $currentLang "en") (eq $target.Lang "fr") -}}
{{- $flag = partial "icons/fr.svg" $target -}}
{{- else if eq $currentLang "fr" -}}
{{- partial "lang-detection.html" $target -}}
{{- if $target.Store.Get "en_article_in_fr_site" -}}
{{- $flag = partial "icons/en.svg" $target -}}
{{- end -}}
{{- end -}}
{{- else if not (strings.HasPrefix $u.Path "/") -}}
{{- /* 3. Relative links */ -}}
{{- $href = printf "%s%s%s" (path.Join $currentPage.RelPermalink $u.Path) (cond (strings.HasSuffix $u.Path "/") "/" "") $fragment -}}
{{- end -}}
{{- end -}}
{{- end -}}
<a href="{{ $href | safeURL }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ $text | safeHTML }}</a>
{{- with $flag }} {{ . | strings.TrimSpace | safeHTML }}{{ end -}}

2
layouts/_partials/article-headline.html

@ -7,5 +7,5 @@
{{ end }} {{ end }}
</a></h4> </a></h4>
{{- partial "post-metadata.html" . -}} {{- partial "post-metadata.html" . -}}
<p>{{ .current.Summary | plainify | htmlUnescape }} <a href="{{ .current.RelPermalink }}" class="continue-reading">{{ i18n "continue_reading" }}</a></p> <p>{{ .current.Summary | replaceRE `(?s)\s*<svg.*?</svg>` "" | plainify | htmlUnescape }} <a href="{{ .current.RelPermalink }}" class="continue-reading">{{ i18n "continue_reading" }}</a></p>
</section> </section>

64
layouts/_partials/head.html

@ -14,13 +14,11 @@
<!-- <!--
-- Core Web Vitals / Cumulative Layout Shift. -- Core Web Vitals / Cumulative Layout Shift.
-- --
-- Preload all fonts and images to reduce layout shifts. -- Preload the fonts used on every page (body text, menu and toolbar).
-- The monospace font is only used by code blocks: it is not preloaded.
--> -->
<link rel="preload" href="/fonts/JetBrainsMono-Regular.woff2" as="font" type="font/woff2" crossorigin="anonymous"> <link rel="preload" href="/fonts/SourceSerifPro-Regular.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/fonts/JetBrainsMono-Regular.woff" as="font" type="font/woff" crossorigin="anonymous"> <link rel="preload" href="/fonts/SourceSansPro-Regular.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/fonts/JetBrainsMono-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous">
<link rel="preload" href="/fonts/SourceSerifPro-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous">
<link rel="preload" href="/fonts/SourceSansPro-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous">
<!-- <!--
-- Add a reference to the favicon.ico. -- Add a reference to the favicon.ico.
@ -32,28 +30,13 @@
-- Ensure the <meta name=description> tag is never empty since it is used -- Ensure the <meta name=description> tag is never empty since it is used
-- in the SEO score. -- in the SEO score.
--> -->
{{- if .IsHome }} <meta name="description" content="{{ partialCached "page-description.html" . .Permalink }}">
{{- with .Site.Params.description }}
<meta name="description" content="{{ . }}">
{{- else }}
<meta name="description" content="{{ .Title }}">
{{- end }}
{{- else }}
{{- with .Params.description }}
<meta name="description" content="{{ . }}">
{{- else }}
<meta name="description" content="{{ .Title }}">
{{- end }}
{{- end }}
<!-- Add all the relevant RSS Feed URLs --> <!-- Add all the relevant RSS Feed URLs -->
{{ with .OutputFormats.Get "rss" -}} {{ with .OutputFormats.Get "rss" -}}
{{ printf `<link rel=%q type=%q href=%q title=%q>` .Rel .MediaType.Type .Permalink site.Title | safeHTML }} {{ printf `<link rel=%q type=%q href=%q title=%q>` .Rel .MediaType.Type .Permalink site.Title | safeHTML }}
{{ end }} {{ end }}
<!-- Tell IE to use the Edge engine -->
<meta name="X-UA-Compatible" content="IE=edge">
<!-- <!--
-- The viewport is the user's visible area of a web page. -- The viewport is the user's visible area of a web page.
-- The viewport varies with the device, and will be smaller on a mobile phone -- The viewport varies with the device, and will be smaller on a mobile phone
@ -73,15 +56,6 @@
{{ partial "opengraph.html" . }} {{ partial "opengraph.html" . }}
{{ partial "twitter_cards.html" . }} {{ partial "twitter_cards.html" . }}
<!-- The search page is the only one to require Javascript -->
{{ if eq .Type "search" }}
<!--
-- Download and copy over fuse.js file from fusejs.io.
-- I used the UMD build, minified, version 6.4.3
-->
<script src="/js/fuse.js"></script>
{{ end }}
{{- if .Site.Params.enablePlausible -}} {{- if .Site.Params.enablePlausible -}}
<script defer data-domain="itix.fr" src="https://plausible.io/js/plausible.js"></script> <script defer data-domain="itix.fr" src="https://plausible.io/js/plausible.js"></script>
{{- end -}} {{- end -}}
@ -117,30 +91,32 @@
-- Structured Data can be tested using the Google Search Console. -- Structured Data can be tested using the Google Search Console.
-- https://search.google.com/test/rich-results -- https://search.google.com/test/rich-results
--> -->
{{- $structuredData := dict -}}
{{- $description := partialCached "page-description.html" . .Permalink -}}
{{- if .IsHome -}} {{- if .IsHome -}}
{{- $.Store.Set "structured_data" (dict "@context" "http://schema.org" "@type" "WebSite" "name" .Site.Title "url" .Site.BaseURL) -}} {{- $structuredData = dict "@context" "https://schema.org" "@type" "WebSite" "name" .Site.Title "url" .Permalink "description" $description "inLanguage" .Language.Lang -}}
{{- with .Site.Params.subtitle -}} {{- else if and .IsPage (in (slice "blog" "speaking" "writing") .Section) -}}
{{- $.Store.SetInMap "structured_data" "description" . -}} {{- $author := dict "@type" "Person" "name" site.Params.author.name "url" site.Params.author.url -}}
{{- $structuredData = dict "@context" "https://schema.org" "@type" (cond (eq .Section "blog") "BlogPosting" "Article") "headline" .Title "description" $description "author" $author "articleSection" .Section -}}
{{- with (partialCached "social-image.html" . .Permalink).image -}}
{{- $structuredData = merge $structuredData (dict "image" (slice .Permalink)) -}}
{{- end -}} {{- end -}}
{{- else if and .IsPage (eq .Section "blog") -}} {{- if .Store.Get "en_article_in_fr_site" -}}
{{- $.Store.Set "structured_data" (dict "@context" "http://schema.org" "@type" "BlogPosting" "headline" .Title "image" (slice) "articleSection" .Section) -}} {{- $structuredData = merge $structuredData (dict "inLanguage" "en") -}}
{{- if and (eq .Lang "fr") (.Store.Get "en_article_in_fr_site") -}}
{{- $.Store.SetInMap "structured_data" "inLanguage" "en" -}}
{{- range first 1 .Translations -}} {{- range first 1 .Translations -}}
{{- $.Store.SetInMap "structured_data" "mainEntityOfPage" (dict "@type" "WebPage" "@id" .Permalink) -}} {{- $structuredData = merge $structuredData (dict "mainEntityOfPage" (dict "@type" "WebPage" "@id" .Permalink)) -}}
{{- end -}} {{- end -}}
{{- else -}} {{- else -}}
{{- $.Store.SetInMap "structured_data" "inLanguage" .Language.Lang -}} {{- $structuredData = merge $structuredData (dict "inLanguage" .Language.Lang "mainEntityOfPage" (dict "@type" "WebPage" "@id" .Permalink)) -}}
{{- $.Store.SetInMap "structured_data" "mainEntityOfPage" (dict "@type" "WebPage" "@id" .Permalink) -}}
{{- end -}} {{- end -}}
{{- if not .PublishDate.IsZero -}} {{- if not .PublishDate.IsZero -}}
{{- $.Store.SetInMap "structured_data" "datePublished" .PublishDate -}} {{- $structuredData = merge $structuredData (dict "datePublished" .PublishDate) -}}
{{- end -}} {{- end -}}
{{- if and (not .Lastmod.IsZero) (ne .Lastmod .PublishDate) -}} {{- if and (not .Lastmod.IsZero) (ne .Lastmod .PublishDate) -}}
{{- $.Store.SetInMap "structured_data" "dateModified" .Lastmod -}} {{- $structuredData = merge $structuredData (dict "dateModified" .Lastmod) -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- with $.Store.Get "structured_data" }} {{- with $structuredData }}
<script type="application/ld+json"> <script type="application/ld+json">
{{- . | jsonify | safeJS -}} {{- . | jsonify | safeJS -}}
</script> </script>

2
layouts/_partials/icons/en.svg

@ -4,7 +4,7 @@
<path d="M0,0 v30 h60 v-30 z"/> <path d="M0,0 v30 h60 v-30 z"/>
</clipPath> </clipPath>
<clipPath id="t"> <clipPath id="t">
<path d="M30,15 h30 v15 z v15 h-30 z h-30 v-15 z v-15 h30 z"/> <path d="M30,15 H60 V30 Z M30,15 V30 H0 Z M30,15 H0 V0 Z M30,15 V0 H60 Z"/>
</clipPath> </clipPath>
<g clip-path="url(#s)"> <g clip-path="url(#s)">
<path d="M0,0 v30 h60 v-30 z" fill="#012169"/> <path d="M0,0 v30 h60 v-30 z" fill="#012169"/>

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 689 B

37
layouts/_partials/lang-detection.html

@ -1,15 +1,22 @@
{{ if and .IsPage .IsTranslated (eq .Lang "fr") }} {{- /*
<!-- Try to compare files by SH256 fingerprint. --> -- Detects the english articles published as-is in the french site and
{{ if and (index .AllTranslations 0).File (index .AllTranslations 1).File }} -- sets "en_article_in_fr_site" in the page store.
{{ $file0 := readFile (index .AllTranslations 0).File.Filename }} --
{{ $file1 := readFile (index .AllTranslations 1).File.Filename }} -- This partial must not output anything since it is also called from
{{ if eq (sha256 $file0) (sha256 $file1) }} -- inline contexts (internalLink shortcode, article headlines).
{{ .Store.Set "en_article_in_fr_site" true }} */ -}}
{{ end }} {{- if and .IsPage .IsTranslated (eq .Lang "fr") -}}
{{ else }} {{- if and (index .AllTranslations 0).File (index .AllTranslations 1).File -}}
<!-- Sometimes there is no file though. In that case, compare the content. --> {{- /* Try to compare files by SHA256 fingerprint. */ -}}
{{ if eq (index .AllTranslations 0).Plain (index .AllTranslations 1).Plain }} {{- $file0 := readFile (index .AllTranslations 0).File.Filename -}}
{{ .Store.Set "en_article_in_fr_site" true }} {{- $file1 := readFile (index .AllTranslations 1).File.Filename -}}
{{ end }} {{- if eq (sha256 $file0) (sha256 $file1) -}}
{{ end }} {{- .Store.Set "en_article_in_fr_site" true -}}
{{ end }} {{- end -}}
{{- else -}}
{{- /* Sometimes there is no file though. In that case, compare the content. */ -}}
{{- if eq (index .AllTranslations 0).Plain (index .AllTranslations 1).Plain -}}
{{- .Store.Set "en_article_in_fr_site" true -}}
{{- end -}}
{{- end -}}
{{- end -}}

82
layouts/_partials/opengraph.html

@ -0,0 +1,82 @@
{{- /*
-- Based on the Hugo embedded opengraph.html partial (v0.167.0).
--
-- Changes: the description comes from page-description.html and the image
-- from social-image.html (JPEG, 1200 px wide max).
*/ -}}
<meta property="og:url" content="{{ .Permalink }}">
{{- with or site.Title site.Params.title | plainify }}
<meta property="og:site_name" content="{{ . }}">
{{- end }}
{{- with or .Title site.Title site.Params.title | plainify }}
<meta property="og:title" content="{{ . }}">
{{- end }}
{{- with partialCached "page-description.html" . .Permalink }}
<meta property="og:description" content="{{ . }}">
{{- end }}
{{- with or .Params.locale site.Language.Locale }}
<meta property="og:locale" content="{{ replace . `-` `_` }}">
{{- end }}
{{- if .IsPage }}
<meta property="og:type" content="article">
{{- with .Section }}
<meta property="article:section" content="{{ . }}">
{{- end }}
{{- $ISO8601 := "2006-01-02T15:04:05-07:00" }}
{{- with .PublishDate }}
<meta property="article:published_time" {{ .Format $ISO8601 | printf "content=%q" | safeHTMLAttr }}>
{{- end }}
{{- with .Lastmod }}
<meta property="article:modified_time" {{ .Format $ISO8601 | printf "content=%q" | safeHTMLAttr }}>
{{- end }}
{{- range .GetTerms "tags" | first 6 }}
<meta property="article:tag" content="{{ .Page.Title | plainify }}">
{{- end }}
{{- else }}
<meta property="og:type" content="website">
{{- end }}
{{- with (partialCached "social-image.html" . .Permalink).image }}
<meta property="og:image" content="{{ .Permalink }}">
<meta property="og:image:type" content="{{ .MediaType.Type }}">
<meta property="og:image:width" content="{{ .Width }}">
<meta property="og:image:height" content="{{ .Height }}">
<meta property="og:image:alt" content="{{ $.Title | plainify }}">
{{- end }}
{{- with .Params.audio }}
{{- range . | first 6 }}
<meta property="og:audio" content="{{ . | absURL }}">
{{- end }}
{{- end }}
{{- with .Params.videos }}
{{- range . | first 6 }}
<meta property="og:video" content="{{ . | absURL }}">
{{- end }}
{{- end }}
{{- range .GetTerms "series" }}
{{- range .Pages | first 7 }}
{{- if ne $ . }}
<meta property="og:see_also" content="{{ .Permalink }}">
{{- end }}
{{- end }}
{{- end }}
{{- with site.Params.social }}
{{- if reflect.IsMap . }}
{{- with .facebook_app_id }}
<meta property="fb:app_id" content="{{ . }}">
{{- else }}
{{- with .facebook_admin }}
<meta property="fb:admins" content="{{ . }}">
{{- end }}
{{- end }}
{{- end }}
{{- end }}

28
layouts/_partials/page-description.html

@ -0,0 +1,28 @@
{{- /*
-- Returns the description of a page (plain text), used by the meta
-- description, Open Graph, Twitter cards and structured data.
--
-- In order of preference:
-- 1. the "description" front matter parameter
-- 2. the description of the site, for the home page
-- 3. "Articles related to <term>" for the taxonomy term pages
-- 4. the summary of the page, truncated to ~160 characters
-- 5. the description of the site
-- 6. the title of the page
*/ -}}
{{- $description := "" -}}
{{- if .Description -}}
{{- $description = .Description -}}
{{- else if and .IsHome site.Params.description -}}
{{- $description = site.Params.description -}}
{{- else if eq .Kind "term" -}}
{{- $description = printf "%s %s" (i18n "articles_related_to" | strings.TrimSpace) .Title -}}
{{- else -}}
{{- $summary := .Summary | replaceRE `(?s)\s*<svg.*?</svg>` "" | plainify | htmlUnescape | replaceRE `\s+` " " | strings.TrimSpace -}}
{{- with $summary -}}
{{- $description = truncate 160 . -}}
{{- else -}}
{{- $description = or site.Params.description .Title -}}
{{- end -}}
{{- end -}}
{{- return ($description | plainify | htmlUnescape | strings.TrimSpace) -}}

2
layouts/_partials/post-metadata.html

@ -3,7 +3,7 @@
{{- with .current.PublishDate }} {{- with .current.PublishDate }}
<span class="publication-date"> <span class="publication-date">
{{ i18n "published_on" }} {{ i18n "published_on" }}
<time itemprop="published-on" date-time="{{ .Format "2006-01-02" }}">{{ .Format (i18n "date_format") }}</time> <time datetime="{{ .Format "2006-01-02" }}">{{ .Format (i18n "date_format") }}</time>
</span> </span>
{{- end }} {{- end }}

81
layouts/_partials/social-image.html

@ -0,0 +1,81 @@
{{- /*
-- Returns the image used when the page is shared on social networks
-- (Open Graph, Twitter cards) and in the structured data, as a dict:
-- - image: the processed image resource
-- - isDefault: true if the default image of the site is used
--
-- The image is chosen in this order:
-- 1. the first image of the "images" front matter parameter
-- 2. a page resource named *feature*, *cover* or *thumbnail*
-- 3. the first image displayed in the page content
-- 4. the first image of the "images" site parameter
--
-- The image is converted to JPEG (WebP is not supported everywhere) and
-- resized to 1200 px wide max, without cropping: the social networks crop
-- it to their own aspect ratio.
*/ -}}
{{- $formats := slice "jpeg" "png" "webp" "gif" "bmp" "tiff" -}}
{{- $images := slice -}}
{{- range .Resources.ByType "image" -}}
{{- if in $formats .MediaType.SubType -}}
{{- $images = $images | append . -}}
{{- end -}}
{{- end -}}
{{- $source := false -}}
{{- $isDefault := false -}}
{{- /* 1. The "images" front matter parameter */ -}}
{{- range $name := .Params.images -}}
{{- if not $source -}}
{{- range $images -}}
{{- if and (not $source) (eq .Name $name) -}}{{- $source = . -}}{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- /* 2. A page resource named *feature*, *cover* or *thumbnail* */ -}}
{{- if not $source -}}
{{- range $images -}}
{{- if and (not $source) (findRE `(?i)(feature|cover|thumbnail)` .Name) -}}{{- $source = . -}}{{- end -}}
{{- end -}}
{{- end -}}
{{- /* 3. The first image displayed in the page content */ -}}
{{- if and (not $source) $images -}}
{{- /*
-- The images are compared by name (without extension) since the image
-- render hook serves processed variants (name_hu_<hash>.webp). Do not use
-- .RelPermalink here: it would publish all the images of the page.
*/ -}}
{{- range findRESubmatch `<img[^>]*\ssrc="([^"]+)"` .Content -}}
{{- $name := index . 1 | path.Base | replaceRE `_hu_[0-9a-f]+\.\w+$` "" | replaceRE `\.\w+$` "" -}}
{{- if not $source -}}
{{- range $images -}}
{{- if and (not $source) (eq (replaceRE `\.\w+$` "" (path.Base .Name)) $name) -}}{{- $source = . -}}{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- /* 4. The default image of the site */ -}}
{{- if not $source -}}
{{- with index site.Params.images 0 -}}
{{- with resources.Get (strings.TrimPrefix "/" .) -}}
{{- $source = . -}}
{{- $isDefault = true -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- $result := dict -}}
{{- with $source -}}
{{- $image := . -}}
{{- if gt .Width 1200 -}}
{{- $image = .Resize "1200x jpg q85" -}}
{{- else -}}
{{- $image = .Process "jpg q85" -}}
{{- end -}}
{{- $result = dict "image" $image "isDefault" $isDefault -}}
{{- end -}}
{{- return $result -}}

1
layouts/_partials/toolbar.html

@ -8,6 +8,7 @@
</div> </div>
{{- end }} {{- end }}
<div class="actions"> <div class="actions">
<!-- "#top" scrolls to the top of the page, no anchor is needed -->
<a href="#top" class="action" title="{{ i18n "back_to_top" }}"> <a href="#top" class="action" title="{{ i18n "back_to_top" }}">
{{- partial "icons/vertical_align_top.svg" . -}} {{- partial "icons/vertical_align_top.svg" . -}}
</a> </a>

36
layouts/_partials/twitter_cards.html

@ -0,0 +1,36 @@
{{- /*
-- Based on the Hugo embedded twitter_cards.html partial (v0.167.0).
--
-- Changes: the description comes from page-description.html and the image
-- from social-image.html (JPEG, 1200 px wide max).
*/ -}}
{{- /* The default image (avatar) is better displayed as a small card. */}}
{{- $social := partialCached "social-image.html" . .Permalink }}
{{- with $social.image }}
<meta name="twitter:card" content="{{ cond $social.isDefault "summary" "summary_large_image" }}">
<meta name="twitter:image" content="{{ .Permalink }}">
<meta name="twitter:image:alt" content="{{ $.Title | plainify }}">
{{- else }}
<meta name="twitter:card" content="summary">
{{- end }}
{{- with or .Title site.Title site.Params.title | plainify }}
<meta name="twitter:title" content="{{ . }}">
{{- end }}
{{- with partialCached "page-description.html" . .Permalink }}
<meta name="twitter:description" content="{{ . }}">
{{- end }}
{{- $twitterSite := "" }}
{{- with site.Params.social }}
{{- if reflect.IsMap . }}
{{- with .twitter }}
{{- $content := . }}
{{- if not (strings.HasPrefix . "@") }}
{{- $content = printf "@%v" . }}
{{- end }}
<meta name="twitter:site" content="{{ $content }}">
{{- end }}
{{- end }}
{{- end }}

11
layouts/_shortcodes/attachedFigure.html

@ -1,11 +0,0 @@
<!-- This shortcode renders an HTML figure based on a page resource -->
{{- $filename := .Get "src" -}}
{{- $image := .Page.Resources.GetMatch (printf "%s" $filename) -}}
<figure>
<img src="{{ $image.RelPermalink }}" alt="{{- .Get "title" | plainify -}}">
{{- with .Get "title" }}
<figcaption>
{{- . | markdownify -}}
</figcaption>
{{- end }}
</figure>

7
layouts/_shortcodes/highlightFile.html

@ -1,7 +0,0 @@
<!-- This shortcode renders an code block prefixed by a filename -->
<div class="highlightedFile">
{{- with .Get 0 -}}
<div class="filename">{{ printf (i18n "file") . | safeHTML }}</div>
{{- end -}}
{{- highlight (trim .Inner "\n") (.Get 1) (.Get 2) -}}
</div>

7
layouts/_shortcodes/highlightWithTitle.html

@ -1,7 +0,0 @@
<!-- This shortcode renders an code block prefixed by a filename -->
<div class="highlightedFile">
{{- with .Get 0 -}}
<div class="filename">{{ . | safeHTML }}</div>
{{- end -}}
{{- highlight (trim .Inner "\n") (.Get 1) (.Get 2) -}}
</div>

31
layouts/_shortcodes/internalLink.html

@ -1,31 +0,0 @@
<!-- This shortcode generates the HTML link to an internal resource, which can be translated or not.
-- It displays a country flag when the target page is in a different language than the current page.
-- Currently, this shortcode can only handle Hugo sites with two languages. -->
{{ $path := .Get "path" }}
{{ $currentLang := .Page.Lang }}
{{ $title := .Get "title" }}
{{ $sites := hugo.Sites }}
{{ if ne $currentLang (index hugo.Sites 0).Language.Lang }}
{{ $sites = collections.Reverse hugo.Sites }}
{{ end }}
{{ range $sites }}
{{ with .GetPage $path }}
<a href="{{ .RelPermalink }}">
{{ if ne $title "" }}
{{ $title }}
{{ else }}
{{ .Title }}
{{ end }}
</a>
{{ if and (eq $currentLang "en") (eq .Lang "fr") }}
{{- partial "icons/fr.svg" . -}}
{{ end }}
{{ if (eq $currentLang "fr") }}
{{- partial "lang-detection.html" . -}}
{{ if .Store.Get "en_article_in_fr_site" }}
{{- partial "icons/en.svg" . -}}
{{ end }}
{{ end }}
{{ break }}
{{ end }}
{{ end }}

6
layouts/_shortcodes/screenshotOf.html

@ -1,6 +0,0 @@
<!-- This shortcode renders an image linked to a resource somewhere -->
{{- $filename := .Get "src" -}}
{{- $image := .Page.Resources.GetMatch (printf "%s" $filename) -}}
<a href="{{- .Get "href" | plainify -}}">
<img src="{{ $image.RelPermalink }}">
</a>

2
layouts/baseof.html

@ -3,8 +3,6 @@
<html lang="{{ .Lang }}"> <html lang="{{ .Lang }}">
{{ partial "head.html" . }} {{ partial "head.html" . }}
<body> <body>
<!-- HTML Anchor used by the button "back to top" -->
<a name="top"></a>
{{ partial "menu.html" . }} {{ partial "menu.html" . }}
{{ partial "toolbar.html" . }} {{ partial "toolbar.html" . }}
<main> <main>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save