k8s
This commit is contained in:
@ -494,27 +494,28 @@ public class ArticlesController
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet(Name = "GetArticles")]
|
[HttpGet(Name = "GetArticles")]
|
||||||
public ArticlesResponse Get([FromQuery] int page = 1, [FromQuery] int size = 10)
|
public async Task<ArticlesResponse> Get([FromQuery] int page = 1, [FromQuery] int size = 10)
|
||||||
{
|
{
|
||||||
var articles = _context.Articles
|
var articles = await _context.Articles
|
||||||
.OrderByDescending(a => a.Id)
|
.OrderByDescending(a => a.Id)
|
||||||
.Skip((page - 1) * size)
|
.Skip((page - 1) * size)
|
||||||
.Take(size)
|
.Take(size)
|
||||||
.ProjectToType<ArticleListDto>();
|
.ProjectToType<ArticleListDto>()
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
var articlesCount = _context.Articles.Count();
|
var articlesCount = await _context.Articles.CountAsync();
|
||||||
|
|
||||||
return new ArticlesResponse(articles, articlesCount);
|
return new ArticlesResponse(articles, articlesCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{slug}", Name = "GetArticleBySlug")]
|
[HttpGet("{slug}", Name = "GetArticleBySlug")]
|
||||||
public ActionResult<ArticleDto> GetBySlug(string slug)
|
public async Task<ActionResult<ArticleDto>> GetBySlug(string slug)
|
||||||
{
|
{
|
||||||
var article = _context.Articles
|
var article = await _context.Articles
|
||||||
.Include(a => a.Author)
|
.Include(a => a.Author)
|
||||||
.Include(a => a.Comments.OrderByDescending(c => c.Id))
|
.Include(a => a.Comments.OrderByDescending(c => c.Id))
|
||||||
.ThenInclude(c => c.Author)
|
.ThenInclude(c => c.Author)
|
||||||
.FirstOrDefault(a => a.Slug == slug);
|
.FirstOrDefaultAsync(a => a.Slug == slug);
|
||||||
|
|
||||||
if (article is null)
|
if (article is null)
|
||||||
{
|
{
|
||||||
@ -812,6 +813,31 @@ public class ArticlesController {
|
|||||||
|
|
||||||
{{< /highlight >}}
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
Let's move Swagger UI under `/api` path too:
|
||||||
|
|
||||||
|
{{< highlight host="kuberocks-demo" file="src/KubeRocks.WebApi/Program.cs" >}}
|
||||||
|
|
||||||
|
```cs
|
||||||
|
//...
|
||||||
|
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSwagger(c =>
|
||||||
|
{
|
||||||
|
c.RouteTemplate = "/api/{documentName}/swagger.json";
|
||||||
|
});
|
||||||
|
app.UseSwaggerUI(c =>
|
||||||
|
{
|
||||||
|
c.SwaggerEndpoint("v1/swagger.json", "KubeRocks v1");
|
||||||
|
c.RoutePrefix = "api";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//...
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
{{< alert >}}
|
{{< alert >}}
|
||||||
You may use ASP.NET API versioning, which work the same way with [versioning URL path](https://github.com/dotnet/aspnet-api-versioning/wiki/Versioning-via-the-URL-Path).
|
You may use ASP.NET API versioning, which work the same way with [versioning URL path](https://github.com/dotnet/aspnet-api-versioning/wiki/Versioning-via-the-URL-Path).
|
||||||
{{< /alert >}}
|
{{< /alert >}}
|
||||||
@ -821,6 +847,7 @@ All is left is to include only the endpoints under `/api` prefix on Traefik Ingr
|
|||||||
{{< highlight host="demo-kube-flux" file="clusters/demo/kuberocks/deploy-demo.yaml" >}}
|
{{< highlight host="demo-kube-flux" file="clusters/demo/kuberocks/deploy-demo.yaml" >}}
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
#...
|
||||||
apiVersion: traefik.io/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
kind: IngressRoute
|
kind: IngressRoute
|
||||||
#...
|
#...
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 93 KiB |
@ -349,7 +349,8 @@ Expose the startup service of minimal API:
|
|||||||
{{< highlight host="kuberocks-demo" file="src/KubeRocks.WebApi/Program.cs" >}}
|
{{< highlight host="kuberocks-demo" file="src/KubeRocks.WebApi/Program.cs" >}}
|
||||||
|
|
||||||
```cs
|
```cs
|
||||||
#...
|
//...
|
||||||
|
|
||||||
public partial class Program
|
public partial class Program
|
||||||
{
|
{
|
||||||
protected Program() { }
|
protected Program() { }
|
||||||
@ -1010,6 +1011,523 @@ Once deployed, relaunch a load test with K6 and admire the DB load balancing in
|
|||||||
|
|
||||||
[](grafana-db-lb.png)
|
[](grafana-db-lb.png)
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
Let's finish this guide by a quick view of SPA frontend development as a separate project from backend.
|
||||||
|
|
||||||
|
### Vue TS
|
||||||
|
|
||||||
|
Create a new Vue.js project:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx degit antfu/vitesse-lite kuberocks-demo-ui
|
||||||
|
cd kuberocks-demo-ui
|
||||||
|
git init
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial commit"
|
||||||
|
# using pnpm, scoop install pnpm
|
||||||
|
pnpm i
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Should launch app in `http://localhost:3333/`. Create a new `kuberocks-demo-ui` Gitea repo and push this code into it. Now lets quick and done for API calls.
|
||||||
|
|
||||||
|
### Get around CORS and HTTPS with YARP
|
||||||
|
|
||||||
|
As always when frontend is separated from backend, we have to deal with CORS. But I prefer to have one single URL for frontend + backend and get rid of CORS problem by simply call under `/api` path. Moreover, it'll be production ready without need to manage any `Vite` variable for API URL and we'll get HTTPS provided by dotnet. Back to API project.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dotnet add src/KubeRocks.WebApi package Yarp.ReverseProxy
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< highlight host="kuberocks-demo" file="src/KubeRocks.WebApi/Program.cs" >}}
|
||||||
|
|
||||||
|
```cs
|
||||||
|
//...
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddReverseProxy()
|
||||||
|
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
|
||||||
|
|
||||||
|
//...
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.MapReverseProxy();
|
||||||
|
|
||||||
|
//...
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
//...
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
Note as we must add `app.UseRouting();` too in order to get Swagger UI working.
|
||||||
|
|
||||||
|
The proxy configuration (only for development):
|
||||||
|
|
||||||
|
{{< highlight host="kuberocks-demo" file="src/KubeRocks.WebApi/appsettings.Development.json" >}}
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
"ReverseProxy": {
|
||||||
|
"Routes": {
|
||||||
|
"ServerRouteApi": {
|
||||||
|
"ClusterId": "Server",
|
||||||
|
"Match": {
|
||||||
|
"Path": "/api/{**catch-all}"
|
||||||
|
},
|
||||||
|
"Transforms": [
|
||||||
|
{
|
||||||
|
"PathRemovePrefix": "/api"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ClientRoute": {
|
||||||
|
"ClusterId": "Client",
|
||||||
|
"Match": {
|
||||||
|
"Path": "{**catch-all}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Clusters": {
|
||||||
|
"Client": {
|
||||||
|
"Destinations": {
|
||||||
|
"Client1": {
|
||||||
|
"Address": "http://localhost:3333"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Server": {
|
||||||
|
"Destinations": {
|
||||||
|
"Server1": {
|
||||||
|
"Address": "https://localhost:7159"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
Now your frontend app should appear under `https://localhost:7159`, and API calls under `https://localhost:7159/api`. We now benefit from HTTPS for all app. Push API code.
|
||||||
|
|
||||||
|
### Typescript API generator
|
||||||
|
|
||||||
|
As we use OpenAPI, it's possible to generate typescript client for API calls. Add this package:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm add openapi-typescript -D
|
||||||
|
pnpm add openapi-typescript-fetch
|
||||||
|
```
|
||||||
|
|
||||||
|
Before generate the client model, go back to backend for fixing default nullable reference from `Swashbuckle.AspNetCore`:
|
||||||
|
|
||||||
|
{{< highlight host="kuberocks-demo" file="src/KubeRocks.WebApi/Filters/RequiredNotNullableSchemaFilter.cs" >}}
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
|
namespace KubeRocks.WebApi.Filters;
|
||||||
|
|
||||||
|
public class RequiredNotNullableSchemaFilter : ISchemaFilter
|
||||||
|
{
|
||||||
|
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
||||||
|
{
|
||||||
|
if (schema.Properties is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var notNullableProperties = schema
|
||||||
|
.Properties
|
||||||
|
.Where(x => !x.Value.Nullable && !schema.Required.Contains(x.Key))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var property in notNullableProperties)
|
||||||
|
{
|
||||||
|
schema.Required.Add(property.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
{{< highlight host="kuberocks-demo" file="src/KubeRocks.WebApi/Program.cs" >}}
|
||||||
|
|
||||||
|
```cs
|
||||||
|
//...
|
||||||
|
|
||||||
|
builder.Services.AddSwaggerGen(o =>
|
||||||
|
{
|
||||||
|
o.SupportNonNullableReferenceTypes();
|
||||||
|
o.SchemaFilter<RequiredNotNullableSchemaFilter>();
|
||||||
|
});
|
||||||
|
|
||||||
|
//...
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
Sadly, without this boring step, many attributes will be nullable in the generated model, which must not be the case. Now generate the model:
|
||||||
|
|
||||||
|
{{< highlight host="kuberocks-demo-ui" file="package.json" >}}
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
"scripts": {
|
||||||
|
//...
|
||||||
|
"openapi": "openapi-typescript http://localhost:5123/api/v1/swagger.json --output src/api/openapi.ts"
|
||||||
|
},
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
Use the HTTP version of swagger as you'll get a self certificate error. The use `pnpm openapi` to generate full TS model. Finally, describe API fetchers like so:
|
||||||
|
|
||||||
|
{{< highlight host="kuberocks-demo-ui" file="src/api/index.ts" >}}
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Fetcher } from 'openapi-typescript-fetch'
|
||||||
|
|
||||||
|
import type { components, paths } from './openapi'
|
||||||
|
|
||||||
|
const fetcher = Fetcher.for<paths>()
|
||||||
|
|
||||||
|
type ArticleList = components['schemas']['ArticleListDto']
|
||||||
|
type Article = components['schemas']['ArticleDto']
|
||||||
|
|
||||||
|
const getArticles = fetcher.path('/api/Articles').method('get').create()
|
||||||
|
const getArticleBySlug = fetcher.path('/api/Articles/{slug}').method('get').create()
|
||||||
|
|
||||||
|
export type { Article, ArticleList }
|
||||||
|
export {
|
||||||
|
getArticles,
|
||||||
|
getArticleBySlug,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
We are now fully typed compliant with the API.
|
||||||
|
|
||||||
|
### Call the API
|
||||||
|
|
||||||
|
Let's create a pretty basic list + detail vue pages:
|
||||||
|
|
||||||
|
{{< highlight host="kuberocks-demo-ui" file="src/pages/articles/index.vue" >}}
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { getArticles } from '~/api'
|
||||||
|
import type { ArticleList } from '~/api'
|
||||||
|
|
||||||
|
const articles = ref<ArticleList[]>([])
|
||||||
|
|
||||||
|
async function loadArticles() {
|
||||||
|
const { data } = await getArticles({
|
||||||
|
page: 1,
|
||||||
|
size: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
articles.value = data.articles
|
||||||
|
}
|
||||||
|
|
||||||
|
loadArticles()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RouterLink
|
||||||
|
v-for="(article, i) in articles"
|
||||||
|
:key="i"
|
||||||
|
:to="`/articles/${article.slug}`"
|
||||||
|
>
|
||||||
|
<h3>{{ article.title }}</h3>
|
||||||
|
</RouterLink>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
{{< highlight host="kuberocks-demo-ui" file="src/pages/articles/[slug].vue" >}}
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { getArticleBySlug } from '~/api'
|
||||||
|
import type { Article } from '~/api'
|
||||||
|
|
||||||
|
const props = defineProps<{ slug: string }>()
|
||||||
|
|
||||||
|
const article = ref<Article>()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
async function getArticle() {
|
||||||
|
const { data } = await getArticleBySlug({ slug: props.slug })
|
||||||
|
|
||||||
|
article.value = data
|
||||||
|
}
|
||||||
|
|
||||||
|
getArticle()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="article">
|
||||||
|
<h1>{{ article.title }}</h1>
|
||||||
|
<p>{{ article.description }}</p>
|
||||||
|
<div>{{ article.body }}</div>
|
||||||
|
<div>
|
||||||
|
<button m-3 mt-8 text-sm btn @click="router.back()">
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
It should work flawlessly.
|
||||||
|
|
||||||
|
### Frontend CI/CD
|
||||||
|
|
||||||
|
The CI frontend is far simpler than backend. Create a new `demo-ui` pipeline:
|
||||||
|
|
||||||
|
{{< highlight host="demo-kube-flux" file="pipelines/demo-ui.yaml" >}}
|
||||||
|
|
||||||
|
```yml
|
||||||
|
resources:
|
||||||
|
- name: version
|
||||||
|
type: semver
|
||||||
|
source:
|
||||||
|
driver: git
|
||||||
|
uri: ((git.url))/kuberocks/demo-ui
|
||||||
|
branch: main
|
||||||
|
file: version
|
||||||
|
username: ((git.username))
|
||||||
|
password: ((git.password))
|
||||||
|
git_user: ((git.git-user))
|
||||||
|
commit_message: ((git.commit-message))
|
||||||
|
- name: source-code
|
||||||
|
type: git
|
||||||
|
icon: coffee
|
||||||
|
source:
|
||||||
|
uri: ((git.url))/kuberocks/demo-ui
|
||||||
|
branch: main
|
||||||
|
username: ((git.username))
|
||||||
|
password: ((git.password))
|
||||||
|
- name: docker-image
|
||||||
|
type: registry-image
|
||||||
|
icon: docker
|
||||||
|
source:
|
||||||
|
repository: ((registry.name))/kuberocks/demo-ui
|
||||||
|
tag: latest
|
||||||
|
username: ((registry.username))
|
||||||
|
password: ((registry.password))
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- name: build
|
||||||
|
plan:
|
||||||
|
- get: source-code
|
||||||
|
trigger: true
|
||||||
|
|
||||||
|
- task: build-source
|
||||||
|
config:
|
||||||
|
platform: linux
|
||||||
|
image_resource:
|
||||||
|
type: registry-image
|
||||||
|
source:
|
||||||
|
repository: node
|
||||||
|
tag: 18-buster
|
||||||
|
inputs:
|
||||||
|
- name: source-code
|
||||||
|
path: .
|
||||||
|
outputs:
|
||||||
|
- name: dist
|
||||||
|
path: dist
|
||||||
|
caches:
|
||||||
|
- path: .pnpm-store
|
||||||
|
run:
|
||||||
|
path: /bin/sh
|
||||||
|
args:
|
||||||
|
- -ec
|
||||||
|
- |
|
||||||
|
corepack enable
|
||||||
|
corepack prepare pnpm@latest-8 --activate
|
||||||
|
pnpm config set store-dir .pnpm-store
|
||||||
|
pnpm i
|
||||||
|
pnpm lint
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
- task: build-image
|
||||||
|
privileged: true
|
||||||
|
config:
|
||||||
|
platform: linux
|
||||||
|
image_resource:
|
||||||
|
type: registry-image
|
||||||
|
source:
|
||||||
|
repository: concourse/oci-build-task
|
||||||
|
inputs:
|
||||||
|
- name: source-code
|
||||||
|
path: .
|
||||||
|
- name: dist
|
||||||
|
path: dist
|
||||||
|
outputs:
|
||||||
|
- name: image
|
||||||
|
run:
|
||||||
|
path: build
|
||||||
|
- put: version
|
||||||
|
params: { bump: patch }
|
||||||
|
- put: docker-image
|
||||||
|
params:
|
||||||
|
additional_tags: version/number
|
||||||
|
image: image/image.tar
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
{{< highlight host="demo-kube-flux" file="pipelines/demo-ui.yaml" >}}
|
||||||
|
|
||||||
|
```tf
|
||||||
|
#...
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- name: configure-pipelines
|
||||||
|
plan:
|
||||||
|
#...
|
||||||
|
- set_pipeline: demo-ui
|
||||||
|
file: ci/pipelines/demo-ui.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
Apply it and put this nginx `Dockerfile` on frontend root project:
|
||||||
|
|
||||||
|
{{< highlight host="kuberocks-demo-ui" file="Dockerfile" >}}
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY dist /usr/share/nginx/html
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
After push all CI should build correctly. Then the image policy for auto update:
|
||||||
|
|
||||||
|
{{< highlight host="demo-kube-flux" file="clusters/demo/kuberocks/images-demo-ui.yaml" >}}
|
||||||
|
|
||||||
|
```yml
|
||||||
|
apiVersion: image.toolkit.fluxcd.io/v1beta1
|
||||||
|
kind: ImageRepository
|
||||||
|
metadata:
|
||||||
|
name: demo-ui
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
image: gitea.kube.rocks/kuberocks/demo-ui
|
||||||
|
interval: 1m0s
|
||||||
|
secretRef:
|
||||||
|
name: dockerconfigjson
|
||||||
|
---
|
||||||
|
apiVersion: image.toolkit.fluxcd.io/v1beta1
|
||||||
|
kind: ImagePolicy
|
||||||
|
metadata:
|
||||||
|
name: demo-ui
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
imageRepositoryRef:
|
||||||
|
name: demo-ui
|
||||||
|
namespace: flux-system
|
||||||
|
policy:
|
||||||
|
semver:
|
||||||
|
range: 0.0.x
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
The deployment:
|
||||||
|
|
||||||
|
{{< highlight host="demo-kube-flux" file="clusters/demo/kuberocks/deploy-demo-ui.yaml" >}}
|
||||||
|
|
||||||
|
```yml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: demo-ui
|
||||||
|
namespace: kuberocks
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: demo-ui
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: demo-ui
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: dockerconfigjson
|
||||||
|
containers:
|
||||||
|
- name: front
|
||||||
|
image: gitea.okami101.io/kuberocks/demo-ui:latest # {"$imagepolicy": "flux-system:image-demo-ui"}
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: demo-ui
|
||||||
|
namespace: kuberocks
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: demo-ui
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
After push, the demo UI container should be deployed. The very last step is to add a new route to existing `IngressRoute` for frontend:
|
||||||
|
|
||||||
|
{{< highlight host="demo-kube-flux" file="clusters/demo/kuberocks/deploy-demo.yaml" >}}
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#...
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
#...
|
||||||
|
spec:
|
||||||
|
#...
|
||||||
|
routes:
|
||||||
|
- match: Host(`demo.kube.rocks`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: demo-ui
|
||||||
|
port: http
|
||||||
|
- match: Host(`demo.kube.rocks`) && PathPrefix(`/api`)
|
||||||
|
#....
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
Go to `https://demo.kube.rocks` to confirm if both app front & back are correctly connected !
|
||||||
|
|
||||||
|
[](frontend.png)
|
||||||
|
|
||||||
## Final check 🎊🏁🎊
|
## Final check 🎊🏁🎊
|
||||||
|
|
||||||
Congratulation if you're getting that far !!!
|
Congratulation if you're getting that far !!!
|
||||||
|
Reference in New Issue
Block a user