From 287cb9c2d0b03ed196ccc5c699a53064a0fb7c6a Mon Sep 17 00:00:00 2001 From: Adrien Beaudouin Date: Sun, 3 Jul 2022 16:13:03 +0200 Subject: [PATCH] kube migration --- content/_data/works/realworld.md | 4 +-- content/_index.md | 45 +++++++++++++++++++------------ data/works.yaml | 7 +++++ layouts/partials/footer.html | 4 ++- layouts/works/single.html | 6 ++++- static/kube.png | Bin 0 -> 15559 bytes 6 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 static/kube.png diff --git a/content/_data/works/realworld.md b/content/_data/works/realworld.md index f4f7e75..2ef4863 100644 --- a/content/_data/works/realworld.md +++ b/content/_data/works/realworld.md @@ -7,8 +7,8 @@ Main purpose of this projects is to have personal extensive API training on mult * Fully tested * High QA by following best practices for linting, formatting, with static analyzers for non strongly typed languages * Community-driven with usage of the most well-known packages -* `MySQL` or `PostgreSQL` as main databases +* `PostgreSQL` as main databases * Use ORM whenever possible that follows any `DataMapper` or `Active Record` patterns * Proper seeder / faker for quick starting with filled DB * Proper suited QA + production Dockerfile -* Complete CI/DC deployment on Docker Swarm with [Drone CI](https://www.drone.io/) +* Complete CI/CD deployment on Kubernetes with [Concourse](https://concourse.okami101.io/) diff --git a/content/_index.md b/content/_index.md index 1fd434c..7945470 100644 --- a/content/_index.md +++ b/content/_index.md @@ -9,38 +9,49 @@ A 🧔🌍💻 aka senior web developer @Bretagne 🇫🇷 Hi folks, I'm a web lover actually working [@Cesson-Sévigné](https://fr.wikipedia.org/wiki/Cesson-S%C3%A9vign%C3%A9), mastering : -* [`ASP.NET Core`](https://docs.microsoft.com/fr-fr/aspnet/core/?view=aspnetcore-6.0) with `C#` and [`Laravel`](https://laravel.com/) as main backend frameworks -* [`Vue 3`](https://vuejs.org/) associated with [`Typescript`](https://www.typescriptlang.org/) and [*Composition API*](https://vuejs.org/guide/extras/composition-api-faq.html) on frontend side -* *Utility-first CSS frameworks* as [`Tailwind`](https://tailwindcss.com/) / [`Windi CSS`](https://windicss.org/), but also comfortable with [`Sass`](https://sass-lang.com/) with **BEM** implementation +* [`ASP.NET Core`](https://docs.microsoft.com/fr-fr/aspnet/core/?view=aspnetcore-6.0) with `C#` and [`Laravel`](https://laravel.com/) as favorite backend frameworks +* [`Vue 3`](https://vuejs.org/) by [*Composition API*](https://vuejs.org/guide/extras/composition-api-faq.html) with [`Typescript`](https://www.typescriptlang.org/) +* *Utility-first CSS frameworks* as [`Tailwind`](https://tailwindcss.com/) / [`Windi CSS`](https://windicss.org/) / [`UnoCSS`](https://github.com/unocss/unocss), but also comfortable with [`Sass`](https://sass-lang.com/) with **BEM** implementation I can develop proper API design following [**DDD / Hexa**](https://en.wikipedia.org/wiki/Domain-driven_design) principles if applicable. In addition to above `.NET` and `PHP` backend stacks, I'm also confident with : -* [`Nest.js`](https://nestjs.com/) associated to [`MikroORM`](https://mikro-orm.io/) (*Typescript*), -* [`FastAPI`](https://fastapi.tiangolo.com/) with [`SQLAlchemy`](https://www.sqlalchemy.org/) (*Python*), -* [`Spring Boot`](https://spring.io/projects/spring-boot) with `Hibernate` as main JPA implementation (*Java*). +* [`Nest.js`](https://nestjs.com/) associated to [`MikroORM`](https://mikro-orm.io/) (*Typescript*) +* [`FastAPI`](https://fastapi.tiangolo.com/) with [`SQLAlchemy`](https://www.sqlalchemy.org/) (*Python*) +* [`Spring Boot`](https://spring.io/projects/spring-boot) with `Hibernate` as main JPA implementation (*Java*) I encourage `TDD` or at least proper **integration tests** on any backend frameworks, following **AAA** aka *Arrange Act Assert* principle : -* `PHPUnit` or [`Pest`](https://pestphp.com/) for *PHP*, -* [`NUnit.net`](https://nunit.org/) or [`xUnit.net`](https://xunit.net/) with [`Fluent Assertions`](https://github.com/fluentassertions/fluentassertions) for *.NET Core*, -* `JUnit` with [`REST Assured`](https://rest-assured.io/) for *Spring Boot*, -* `Jest` and `pytest` on respective *NodeJS* end *Python* stacks. +* `PHPUnit` or [`Pest`](https://pestphp.com/) for *PHP* +* [`NUnit.net`](https://nunit.org/) or [`xUnit.net`](https://xunit.net/) with [`Fluent Assertions`](https://github.com/fluentassertions/fluentassertions) for *.NET Core* +* `JUnit` with [`REST Assured`](https://rest-assured.io/) for *Spring Boot* +* `Jest` and `pytest` on respective *NodeJS* end *Python* stacks -Fully embracing app containerization with `Docker`, from local, staging to production, I push to use properly configured **CI/CD** whenever is possible in order to enforce **continuous automatized testing, linting and coding standard** at many languages ([`ESLint`](https://eslint.org/), [`Prettier`](https://prettier.io/), [`PHP CS fixer`](https://cs.symfony.com/), [`PHPStan`](https://github.com/phpstan/phpstan), [`Black`](https://black.readthedocs.io/en/stable/), [`mypy`](http://mypy-lang.org/), [`Google Java Format`](https://github.com/google/google-java-format), [`Spotless`](https://github.com/diffplug/spotless), and so on...). +Fully embracing app containerization with `Docker` and `Kubernetes`, from local, staging to production, I push to use properly configured **CI/CD** whenever possible in order to enforce **continuous automatized testing, linting and code styling** at many languages ([`ESLint`](https://eslint.org/), [`Prettier`](https://prettier.io/), [`PHP CS fixer`](https://cs.symfony.com/), [`PHPStan`](https://github.com/phpstan/phpstan), [`Black`](https://black.readthedocs.io/en/stable/), [`mypy`](http://mypy-lang.org/), [`Google Java Format`](https://github.com/google/google-java-format), [`Spotless`](https://github.com/diffplug/spotless), and so on...). -Mastering installations of properly configured `Docker Swarm` clusters with next containerized tools : +Mastering installations and maintenance of `Docker Swarm` or bare metal `Kubernetes` clusters. Here some cloud native tools solutions I generally use : -* [`Traefik`](https://traefik.io/traefik/) as main cloud proxy with automatic service discovery and SSL configuration +* [`Traefik`](https://traefik.io/traefik/) as preferred cloud proxy with automatic service discovery and SSL configuration * [`Portainer`](https://www.portainer.io/) as simple GUI for containers management * [`Loki`](https://grafana.com/oss/loki/), [`Prometheus`](https://prometheus.io) and [`Jaeger`](https://www.jaegertracing.io/) as respective *logging*, *metrics* and *tracing* tools -* [`Grafana`](https://grafana.com) as perfect GUI dashboard builder for *Ops* -* [`GitLab`](https://about.gitlab.com/) or [`Gitea`](https://gitea.io/) coupled with [`Drone CI`](https://www.drone.io/) as both *self-hosted CI/CD* solutions -* [SonarQube](https://www.sonarqube.org/) for automatic quality code scan +* [`Grafana`](https://grafana.com) as GUI dashboard builder, designed for *Ops* +* [`Gitea`](https://gitea.io/) or [`GitLab`](https://about.gitlab.com/) as self-hosted *VCS* +* [`Drone CI`](https://www.drone.io/) or [`Concourse`](https://concourse-ci.org/) as both *CI/CD* solutions +* [`SonarQube`](https://www.sonarqube.org/) for automatic quality code scan -I use managed [`PostgreSQL`](https://www.postgresql.org/), [`MySQL`](https://www.mysql.com/fr/), and `MSSQL` as main **SGBD**, as well as `Redis` for high performance cache/sessions management. +I use [`PostgreSQL`](https://www.postgresql.org/), [`MySQL`](https://www.mysql.com/fr/), and `MSSQL` as main *SGBD*, as well as `Redis` for high performance cache/sessions management. For *load testing*, I can write scenarios for both [`K6`](https://k6.io/) and [`Locust`](https://locust.io/), coupled with proper time series DB as [`InfluxDB`](https://www.influxdata.com/) and `Grafana` as visualization tool. For advanced application performance analysis, I tend to use [`OpenTelemetry`](https://opentelemetry.io/) as collection tools for proper metrics that can be exposed to `Prometheus`, and tracing, ready to export into `Jaeger`. Have some experiences with many mid-range cloud providers as [Digital Ocean](https://www.digitalocean.com/), [Hetzner](https://www.hetzner.com/), [OVH](https://www.ovhcloud.com/), [Scaleway](https://www.scaleway.com/). +Some notes of this blog : + +* Hosted on Hetzner Cloud +* Powered by [`Hugo`](https://gohugo.io/) +* Source code on my own [`Gitea`](https://gitea.okami101.io/adr1enbe4udou1n/blog) +* Automatically deployed through my own [`Concourse`](https://concourse.okami101.io) instance ([see pipeline](https://concourse.okami101.io/teams/main/pipelines/blog)) +* Tracked with [`Matomo`](https://matomo.okami101.io/) + +All the above tools are **self-hosted** on custom multi-nodes **bare metal** `Kubernetes` installed with [`k0s`](https://k0sproject.io/). + See some of [my open sourced works]({{< ref "works" >}} "Okami101 Works"). diff --git a/data/works.yaml b/data/works.yaml index 5d12b8d..720953c 100644 --- a/data/works.yaml +++ b/data/works.yaml @@ -7,6 +7,7 @@ - name: vue-ts title: Vue 3 TS Realworld repo: adr1enbe4udou1n/vue-ts-realworld-example-app + ci: conduit-vue-ts demo: https://vuetsrealworld.okami101.io color: green @@ -15,35 +16,41 @@ - name: aspnet-core title: ASP.NET Core Realworld repo: adr1enbe4udou1n/aspnetcore-realworld-example-app + ci: conduit-aspnet-core demo: https://aspnetcorerealworld.okami101.io/api - name: spring-boot title: Spring Boot Realworld repo: adr1enbe4udou1n/spring-boot-realworld-example-app + ci: conduit-spring-boot demo: https://springbootrealworld.okami101.io/api color: green - name: symfony title: Symfony Realworld repo: adr1enbe4udou1n/symfony-realworld-example-app + ci: conduit-symfony demo: https://symfonyrealworld.okami101.io/api color: black - name: laravel title: Laravel Realworld repo: adr1enbe4udou1n/laravel-realworld-example-app + ci: conduit-laravel demo: https://laravelrealworld.okami101.io/api color: orange - name: nestjs title: NestJS Realworld repo: adr1enbe4udou1n/nestjs-realworld-example-app + ci: conduit-nestjs demo: https://nestjsrealworld.okami101.io/api color: red - name: fastapi title: FastAPI Realworld repo: adr1enbe4udou1n/fastapi-realworld-example-app + ci: conduit-fastapi demo: https://fastapirealworld.okami101.io/api color: teal diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index f4f3b18..ee2d706 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -20,6 +20,8 @@ {{ end }}
+ Kubernetes + {{/* Copyright */}} - + No to Web3
{{/* Appearance switch */}} diff --git a/layouts/works/single.html b/layouts/works/single.html index f2b20d8..049bba9 100644 --- a/layouts/works/single.html +++ b/layouts/works/single.html @@ -31,7 +31,7 @@ "red" "border-red-500" "teal" "border-teal-500" "primary" "border-purple-500" }}
- +

{{ .title }}

@@ -48,6 +48,10 @@
{{ partial "button.html" (dict "text" (partial "icon.html" "github") "href" (print "https://github.com/" .repo) "color" .color) }} + {{ if .ci }} + {{ partial "button.html" (dict "text" (partial "icon.html" "bug") "href" (print + "https://concourse.okami101.io/teams/main/pipelines/" .ci) "color" .color) }} + {{ end }} {{ if .demo }} {{ partial "button.html" (dict "text" "Demo" "href" .demo "color" .color) }} {{ end }} diff --git a/static/kube.png b/static/kube.png new file mode 100644 index 0000000000000000000000000000000000000000..9f36a74331a9eb9a5ca5750e8061c8e186b9fd31 GIT binary patch literal 15559 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