cd
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 276 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 80 KiB |
@ -164,6 +164,14 @@ Note as the blobs of image is always physically in the disk, even when "deleted"
|
||||
For that execute `registry garbage-collect /etc/docker/registry/config.yml` inside docker registry.
|
||||
{{< /alert >}}
|
||||
|
||||
### Register registry in Portainer
|
||||
|
||||
For our future app deployments from our built docker image, we need to register our new private registry with proper credentials. Go to *Registries* menu of Portainer, and create the new registry.
|
||||
|
||||
[](portainer-registries.png)
|
||||
|
||||
Save it, and you have registered repository, which will allow proper pulling from it when next stack developments.
|
||||
|
||||
## CI/CD with Drone 🪁
|
||||
|
||||
It's finally time to use our currently unused `runner-01` ! We'll use Drone as free self-hosted solution for docker image building. The all CI/CD process can be summarized to this flow chart :
|
||||
@ -214,8 +222,8 @@ services:
|
||||
DRONE_GITEA_CLIENT_SECRET:
|
||||
DRONE_GITEA_SERVER: https://gitea.sw.okami101.io
|
||||
DRONE_RPC_SECRET:
|
||||
DRONE_SERVER_HOST: drone.sw.okami101.io
|
||||
DRONE_SERVER_PROTO: https
|
||||
DRONE_SERVER_HOST:
|
||||
DRONE_SERVER_PROTO:
|
||||
DRONE_USER_CREATE: username:adr1enbe4udou1n,admin:true
|
||||
networks:
|
||||
- traefik_public
|
||||
@ -251,9 +259,11 @@ Don't forget to have proper docker labels on nodes, as explain [here]({{< ref "0
|
||||
|
||||
| variable | description |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `DRONE_GITEA_CLIENT_ID` | Use the above client ID token |
|
||||
| `DRONE_GITEA_CLIENT_SECRET` | Use the above client secret token |
|
||||
| `DRONE_DATABASE_PASSWORD` | Use the database password |
|
||||
| `DRONE_SERVER_HOST` | The host of main Drone server. I'll use `drone.sw.okami101.io` here. |
|
||||
| `DRONE_SERVER_PROTO` | The scheme protocol, which is `https`. |
|
||||
| `DRONE_GITEA_CLIENT_ID` | Use the above client ID token. |
|
||||
| `DRONE_GITEA_CLIENT_SECRET` | Use the above client secret token. |
|
||||
| `DRONE_DATABASE_PASSWORD` | Use the database password. |
|
||||
| `DRONE_RPC_SECRET` | Necessary for proper secured authentication between Drone and runners. Use `openssl rand -hex 16` for generating a valid token. |
|
||||
| `DRONE_USER_CREATE` | The initial user to create at launch. Put your Gitea username here for setting automatically Gitea user as drone administrator. |
|
||||
|
||||
@ -265,6 +275,204 @@ Finalize registration, and you should finally arrive to main Drone dashboard. If
|
||||
|
||||
[](drone-dashboard.png)
|
||||
|
||||
{{< alert >}}
|
||||
Ensure that the runner detect the remote Drone server before continue. You can check through Grafana logs for any `successfully pinged the remote server`.
|
||||
{{< /alert >}}
|
||||
|
||||
## Test with basic project
|
||||
|
||||
We have all the minimal CI/CD pipeline in place ! Let's create a basic backend Dotnet API and test all CI/CD workflow !
|
||||
|
||||
### Create new API project ✨
|
||||
|
||||
Firstly, create a new `my-weather-api` private repository through Gitea. I'll use `main` as default branch here.
|
||||
|
||||
[](gitea-empty-repository.png)
|
||||
|
||||
Next I presume you have [.NET SDK](https://dotnet.microsoft.com/en-us/download) locally installed. Create a new ASP.NET Core Web API project and push into Gitea.
|
||||
|
||||
```sh
|
||||
dotnet new webapi -o my-weather-api --no-https
|
||||
|
||||
cd my-weather-api
|
||||
|
||||
dotnet new gitignore
|
||||
|
||||
git init
|
||||
git add .
|
||||
git commit -m "first commit"
|
||||
git remote add origin git@gitea.sw.okami101.io:adr1enbe4udou1n/my-weather-api.git # if you use ssh
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
{{< alert >}}
|
||||
Don't forget `--no-https` as we'll use Traefik as main SSL provider, otherwise the app will not properly respond when deployed. It will simply skip the `app.UseHttpsRedirection();` line code middleware.
|
||||
{{< /alert >}}
|
||||
|
||||
Project should be pushed correctly :
|
||||
|
||||
[](gitea-new-repository.png)
|
||||
|
||||
### Drone configuration 🛠️
|
||||
|
||||
Let's now activate the repo in Drone. Click on *SYNC* button. Click on new repo and activate the repository.
|
||||
|
||||
[](drone-repository-settings.png)
|
||||
|
||||
It will create a webhook inside repository settings, triggered on every code push.
|
||||
|
||||
Now generate a new SSH key on `manager-01` :
|
||||
|
||||
```sh
|
||||
ssh-keygen -t ed25519 -C "admin@sw.okami101.io"
|
||||
cat .ssh/id_ed25519 # the private key to set in swarm_ssh_key
|
||||
cat .ssh/id_ed25519.pub # the public key to add just below
|
||||
echo "ssh-ed25519 AAAA... admin@sw.okami101.io" | tee -a .ssh/authorized_keys
|
||||
```
|
||||
|
||||
Then configure the repository settings on Drone. Go to *Organization > Secrets* section and add some global secrets.
|
||||
|
||||
| name | description |
|
||||
| ------------------- | ------------------------------------------ |
|
||||
| `registry_username` | The username access of our docker registry |
|
||||
| `registry_password` | The password access of our docker registry |
|
||||
| `swarm_ssh_key` | The **private** above key |
|
||||
|
||||
[](drone-secrets.png)
|
||||
|
||||
### Configure the pipelines (the CI part) 🏗️
|
||||
|
||||
For working, Drone needs a `.drone.yml` file in root of repository. This file will describe all steps of our build pipeline. Let's create and explain it :
|
||||
|
||||
```yml
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: mcr.microsoft.com/dotnet/sdk:6.0
|
||||
commands:
|
||||
- dotnet publish -c Release -o ./publish
|
||||
|
||||
- name: image
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: registry.sw.okami101.io
|
||||
repo: registry.sw.okami101.io/adr1enbe4udou1n/my-weather-api
|
||||
tags: latest
|
||||
username:
|
||||
from_secret: registry_username
|
||||
password:
|
||||
from_secret: registry_password
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
```
|
||||
|
||||
It's just simple 2 steps :
|
||||
|
||||
1. `build` : Here is the step for project dependencies import, compilation, testing, and code linting / formatting. This is a very basic project here, so we start with a simple building. The image `mcr.microsoft.com/dotnet/sdk:6.0` is the required docker image for proper .NET building. The publish command will generate a `publish` subdirectory.
|
||||
2. `image` : We use the [official docker plugin](https://plugins.drone.io/drone-plugins/drone-docker/) for building our final production based docker image, done from the below Dockerfile. Use `repo` as the final docker image name.
|
||||
|
||||
Next create the Dockerfile which will be used for `image` step :
|
||||
|
||||
```Dockerfile
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:6.0
|
||||
RUN apt-get install -y tzdata
|
||||
|
||||
COPY /publish /app
|
||||
WORKDIR /app
|
||||
|
||||
ENTRYPOINT ["dotnet", "my-weather-api.dll"]
|
||||
```
|
||||
|
||||
We use production suited .NET runtime image `mcr.microsoft.com/dotnet/aspnet:6.0`. Note as **WE MUST** do the simplest commands possible in order to have the lightest image layers, as it's the production image. All we have to do is to copy the final published binaries from above `build` drone step.
|
||||
|
||||
Commit both above files and push to remote repo. Drone should be automatically triggered for building and activate the runner. The runner will clone the project and process all pipeline's steps.
|
||||
|
||||
[](drone-build.png)
|
||||
|
||||
If all's going well, the final image should be pushed in our docker registry. You can ensure it by navigating to <https://registry.sw.okami101.io>.
|
||||
|
||||
### Deployment (the CD part) 🚀
|
||||
|
||||
Our application is now ready for production deployment ! Let's create our new shiny `weather` stack :
|
||||
|
||||
```yml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: registry.sw.okami101.io/adr1enbe4udou1n/my-weather-api
|
||||
environment:
|
||||
ASPNETCORE_ENVIRONMENT: Development
|
||||
networks:
|
||||
- traefik_public
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.services.my-weather-api.loadbalancer.server.port=80
|
||||
placement:
|
||||
constraints:
|
||||
- node.labels.environment == production
|
||||
|
||||
networks:
|
||||
traefik_public:
|
||||
external: true
|
||||
```
|
||||
|
||||
{{< alert >}}
|
||||
I use `Development` in order to have the swagger UI.
|
||||
Be sure to have registered the private registry in Portainer before deploying as [explained here](#register-registry-in-portainer).
|
||||
{{< /alert >}}
|
||||
|
||||
Finally, deploy and see the result in <https://weather.sw.okami101.io/swagger>. You should access to the swagger UI, and API endpoints should correctly respond.
|
||||
|
||||
#### Continuous deployment
|
||||
|
||||
Now it's clear that we don't want to deploy manually every time when the code is pushed.
|
||||
|
||||
First be sure that following `docker service update --image registry.sw.okami101.io/adr1enbe4udou1n/my-weather-api:latest weather_app --with-registry-auth` command works well in `manager-01`. It's simply update the current `weather_app` service with the last available image version from the private registry.
|
||||
|
||||
Now we must be sure that the `runner-01` host can reach the `manager-01` server from outside. If you have applied the firewall at the beginning of this tutorial, only our own IP is authorized. Let's add the public IP of `runner-01` to your `firewall-external` inside Hetzner console.
|
||||
|
||||
Now let's add a new `deploy` step inside `.drone.yml` into our pipeline for automatic deployment !
|
||||
|
||||
```yml
|
||||
#...
|
||||
- name: deploy
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host: sw.okami101.io
|
||||
port: 2222
|
||||
username: swarm
|
||||
key:
|
||||
from_secret: swarm_ssh_key
|
||||
script:
|
||||
- docker service update --image registry.sw.okami101.io/adr1enbe4udou1n/my-weather-api:latest weather_app --with-registry-auth
|
||||
#...
|
||||
```
|
||||
|
||||
The as example edit `Program.cs` file and change next line :
|
||||
|
||||
```cs
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "Weather API",
|
||||
Version = "0.0.1",
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Push and back to your API, and the title and version should be automatically updated !
|
||||
|
||||
[](weather-api.png)
|
||||
|
||||
## SonarQube 📈
|
||||
|
||||
## Tracing with Jaeger with OpenTelemetry 🕰️
|
||||
|
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 32 KiB |