Deploy browser pre-alpha
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
.git
|
||||||
|
.gitea
|
||||||
|
.github
|
||||||
|
.godot
|
||||||
|
**/bin
|
||||||
|
**/obj
|
||||||
|
build
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
name: Deploy STEP3 Web Game
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: self-hosted
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE_TAR: /tmp/step3-web.tar
|
||||||
|
NODES: "192.168.86.72 192.168.86.73 192.168.86.74"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set build identity
|
||||||
|
run: |
|
||||||
|
echo "BUILD_TAG=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Build Web game image
|
||||||
|
run: |
|
||||||
|
docker build -f web/Dockerfile -t "step3-web:${BUILD_TAG}" .
|
||||||
|
|
||||||
|
- name: Save image
|
||||||
|
run: |
|
||||||
|
docker save "step3-web:${BUILD_TAG}" -o "${IMAGE_TAR}"
|
||||||
|
|
||||||
|
- name: Import image on Kubernetes nodes
|
||||||
|
run: |
|
||||||
|
for node in ${NODES}; do
|
||||||
|
scp -o StrictHostKeyChecking=no "${IMAGE_TAR}" hz@"$node":/tmp/step3-web.tar
|
||||||
|
ssh -o StrictHostKeyChecking=no hz@"$node" "sudo ctr -n k8s.io images import /tmp/step3-web.tar && rm -f /tmp/step3-web.tar"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Clean runner image archive
|
||||||
|
if: always()
|
||||||
|
run: rm -f "${IMAGE_TAR}"
|
||||||
|
|
||||||
|
- name: Configure kubeconfig
|
||||||
|
env:
|
||||||
|
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
||||||
|
run: |
|
||||||
|
if [ -n "$KUBECONFIG_CONTENT" ]; then
|
||||||
|
mkdir -p /tmp/kube
|
||||||
|
printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config
|
||||||
|
echo "KUBECONFIG=/tmp/kube/config" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Deploy Web game
|
||||||
|
run: |
|
||||||
|
kubectl create namespace step3-web --dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
kubectl apply -f web/k8s/service.yaml -n step3-web
|
||||||
|
sed "s|step3-web:latest|step3-web:${BUILD_TAG}|" web/k8s/deployment.yaml | kubectl apply -n step3-web -f -
|
||||||
|
kubectl rollout status deployment/step3-web -n step3-web --timeout=180s
|
||||||
@@ -32,6 +32,13 @@ The Godot client reads this from `project.godot`:
|
|||||||
step3/questions_api_url
|
step3/questions_api_url
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Browser Pre-Alpha
|
||||||
|
|
||||||
|
The Web export is deployed at `https://doctor.ranaze.com` through the existing
|
||||||
|
Cloudflare, Traefik, and Kubernetes stack. See
|
||||||
|
[WEB_DEPLOYMENT.md](WEB_DEPLOYMENT.md) for DNS, routing, deployment, and
|
||||||
|
verification details.
|
||||||
|
|
||||||
## Question Bank PDF Imports
|
## Question Bank PDF Imports
|
||||||
|
|
||||||
Use [QBANK_PDF_IMPORT_RUNBOOK.md](QBANK_PDF_IMPORT_RUNBOOK.md) for the complete
|
Use [QBANK_PDF_IMPORT_RUNBOOK.md](QBANK_PDF_IMPORT_RUNBOOK.md) for the complete
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Browser Pre-Alpha Deployment
|
||||||
|
|
||||||
|
The browser build is served at `https://doctor.ranaze.com`. Gitea builds and
|
||||||
|
deploys the Web image to the `step3-web` Kubernetes namespace on every push to
|
||||||
|
`main`. The service is exposed on NodePort `30089`.
|
||||||
|
|
||||||
|
## Cloudflare
|
||||||
|
|
||||||
|
Create a proxied `doctor` DNS record that targets the same public endpoint as
|
||||||
|
the existing `s3q` and `pauth` records. Keep SSL/TLS mode consistent with those
|
||||||
|
working services and redirect HTTP to HTTPS.
|
||||||
|
|
||||||
|
## Traefik
|
||||||
|
|
||||||
|
Create three routers for `doctor.ranaze.com`, ordered by priority:
|
||||||
|
|
||||||
|
1. `Host(`doctor.ranaze.com`) && PathPrefix(`/api/Auth`)` routes to the same
|
||||||
|
backend service used by `pauth.ranaze.com`.
|
||||||
|
2. `Host(`doctor.ranaze.com`) && PathPrefix(`/api`)` routes to the Kubernetes
|
||||||
|
nodes on port `30088`, the Questions API NodePort.
|
||||||
|
3. `Host(`doctor.ranaze.com`)` routes to the Kubernetes nodes on port `30089`,
|
||||||
|
the Web game NodePort.
|
||||||
|
|
||||||
|
Do not strip any prefixes. Give the auth router the highest priority, the
|
||||||
|
general API router the next-highest priority, and the Web router the lowest.
|
||||||
|
Reuse the existing HTTPS entrypoint, certificate resolver, load-balancing node
|
||||||
|
list, and HTTP-to-HTTPS middleware.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
After DNS and Traefik are configured:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsS https://doctor.ranaze.com/healthz
|
||||||
|
curl -i -X OPTIONS https://doctor.ranaze.com/api/Auth/login
|
||||||
|
curl -i -X OPTIONS https://doctor.ranaze.com/api/questions/search
|
||||||
|
```
|
||||||
|
|
||||||
|
The health endpoint must return `ok`. Open the site in a desktop browser and
|
||||||
|
verify registration or login, question loading, images, answer submission,
|
||||||
|
ratings, and question reporting. Browser requests must remain on
|
||||||
|
`doctor.ranaze.com`; requests to `pauth.ranaze.com` or `s3q.ranaze.com` indicate
|
||||||
|
that the Web origin resolver did not run.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
[preset.0]
|
||||||
|
|
||||||
|
name="Web"
|
||||||
|
platform="Web"
|
||||||
|
runnable=true
|
||||||
|
advanced_options=false
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="build/web/index.html"
|
||||||
|
script_export_mode=2
|
||||||
|
|
||||||
|
[preset.0.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
variant/extensions_support=false
|
||||||
|
variant/thread_support=false
|
||||||
|
vram_texture_compression/for_desktop=true
|
||||||
|
vram_texture_compression/for_mobile=true
|
||||||
|
html/export_icon=true
|
||||||
|
html/custom_html_shell=""
|
||||||
|
html/head_include="<meta name=\"description\" content=\"RanaZe's Doctor Maker pre-alpha\">"
|
||||||
|
html/canvas_resize_policy=2
|
||||||
|
html/focus_canvas_on_start=true
|
||||||
|
html/experimental_virtual_keyboard=false
|
||||||
|
progressive_web_app/enabled=false
|
||||||
+5
-1
@@ -12,7 +12,7 @@ config_version=5
|
|||||||
|
|
||||||
config/name="step3"
|
config/name="step3"
|
||||||
run/main_scene="res://scenes/main.tscn"
|
run/main_scene="res://scenes/main.tscn"
|
||||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
config/features=PackedStringArray("4.6", "GL Compatibility")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
[autoload]
|
[autoload]
|
||||||
@@ -28,6 +28,8 @@ BlockSessionService="*res://scripts/autoload/block_session_service.gd"
|
|||||||
|
|
||||||
window/size/viewport_width=1920
|
window/size/viewport_width=1920
|
||||||
window/size/viewport_height=1080
|
window/size/viewport_height=1080
|
||||||
|
window/stretch/mode="canvas_items"
|
||||||
|
window/stretch/aspect="expand"
|
||||||
|
|
||||||
[physics]
|
[physics]
|
||||||
|
|
||||||
@@ -35,6 +37,8 @@ window/size/viewport_height=1080
|
|||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
|
renderer/rendering_method="gl_compatibility"
|
||||||
|
renderer/rendering_method.mobile="gl_compatibility"
|
||||||
rendering_device/driver.windows="d3d12"
|
rendering_device/driver.windows="d3d12"
|
||||||
|
|
||||||
[step3]
|
[step3]
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
|
||||||
|
static func resolve(native_url: String, same_origin_path: String) -> String:
|
||||||
|
if not OS.has_feature("web"):
|
||||||
|
return native_url
|
||||||
|
var browser_origin: Variant = JavaScriptBridge.eval("window.location.origin", true)
|
||||||
|
var origin := str(browser_origin).strip_edges().rstrip("/")
|
||||||
|
if origin.is_empty() or not origin.begins_with("http"):
|
||||||
|
return native_url
|
||||||
|
return "%s/%s" % [origin, same_origin_path.trim_prefix("/")]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c0m4jbetdkadj
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
const EndpointConfig := preload("res://scripts/autoload/api_endpoint_config.gd")
|
||||||
const DEFAULT_QUESTIONS_API_URL := "https://s3q.ranaze.com/api/questions/search"
|
const DEFAULT_QUESTIONS_API_URL := "https://s3q.ranaze.com/api/questions/search"
|
||||||
const DEFAULT_QUESTION_ATTEMPT_API_URL := "https://s3q.ranaze.com/api/questions/attempts/first"
|
const DEFAULT_QUESTION_ATTEMPT_API_URL := "https://s3q.ranaze.com/api/questions/attempts/first"
|
||||||
const DEFAULT_QUESTION_API_BASE_URL := "https://s3q.ranaze.com/api/questions"
|
const DEFAULT_QUESTION_API_BASE_URL := "https://s3q.ranaze.com/api/questions"
|
||||||
@@ -44,11 +45,21 @@ var active_attempt_index := -1
|
|||||||
var attempt_request_active := false
|
var attempt_request_active := false
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
questions_api_url = str(ProjectSettings.get_setting("step3/questions_api_url", DEFAULT_QUESTIONS_API_URL))
|
questions_api_url = EndpointConfig.resolve(
|
||||||
question_attempt_api_url = str(ProjectSettings.get_setting("step3/question_attempt_api_url", DEFAULT_QUESTION_ATTEMPT_API_URL))
|
str(ProjectSettings.get_setting("step3/questions_api_url", DEFAULT_QUESTIONS_API_URL)),
|
||||||
question_api_base_url = str(ProjectSettings.get_setting("step3/question_api_base_url", DEFAULT_QUESTION_API_BASE_URL))
|
"/api/questions/search")
|
||||||
ratings_api_url = str(ProjectSettings.get_setting("step3/ratings_api_url", DEFAULT_RATINGS_API_URL))
|
question_attempt_api_url = EndpointConfig.resolve(
|
||||||
question_reports_api_url = str(ProjectSettings.get_setting("step3/question_reports_api_url", DEFAULT_QUESTION_REPORTS_API_URL))
|
str(ProjectSettings.get_setting("step3/question_attempt_api_url", DEFAULT_QUESTION_ATTEMPT_API_URL)),
|
||||||
|
"/api/questions/attempts/first")
|
||||||
|
question_api_base_url = EndpointConfig.resolve(
|
||||||
|
str(ProjectSettings.get_setting("step3/question_api_base_url", DEFAULT_QUESTION_API_BASE_URL)),
|
||||||
|
"/api/questions")
|
||||||
|
ratings_api_url = EndpointConfig.resolve(
|
||||||
|
str(ProjectSettings.get_setting("step3/ratings_api_url", DEFAULT_RATINGS_API_URL)),
|
||||||
|
"/api/ratings/me")
|
||||||
|
question_reports_api_url = EndpointConfig.resolve(
|
||||||
|
str(ProjectSettings.get_setting("step3/question_reports_api_url", DEFAULT_QUESTION_REPORTS_API_URL)),
|
||||||
|
"/api/question-reports")
|
||||||
request = HTTPRequest.new()
|
request = HTTPRequest.new()
|
||||||
request.request_completed.connect(_on_api_questions_loaded)
|
request.request_completed.connect(_on_api_questions_loaded)
|
||||||
add_child(request)
|
add_child(request)
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
extends VBoxContainer
|
extends VBoxContainer
|
||||||
|
|
||||||
const Style := preload("res://scripts/ui/ui_style.gd")
|
const Style := preload("res://scripts/ui/ui_style.gd")
|
||||||
const AUTH_LOGIN_URL := "https://pauth.ranaze.com/api/Auth/login"
|
const EndpointConfig := preload("res://scripts/autoload/api_endpoint_config.gd")
|
||||||
const AUTH_REGISTER_URL := "https://pauth.ranaze.com/api/Auth/register"
|
const DEFAULT_AUTH_LOGIN_URL := "https://pauth.ranaze.com/api/Auth/login"
|
||||||
|
const DEFAULT_AUTH_REGISTER_URL := "https://pauth.ranaze.com/api/Auth/register"
|
||||||
|
|
||||||
var main: Control
|
var main: Control
|
||||||
var auth_panel: PanelContainer
|
var auth_panel: PanelContainer
|
||||||
@@ -25,9 +26,13 @@ var register_request: HTTPRequest
|
|||||||
var auth_mode := "login"
|
var auth_mode := "login"
|
||||||
var automatic_login_pending := false
|
var automatic_login_pending := false
|
||||||
var login_attempt_username := ""
|
var login_attempt_username := ""
|
||||||
|
var auth_login_url := DEFAULT_AUTH_LOGIN_URL
|
||||||
|
var auth_register_url := DEFAULT_AUTH_REGISTER_URL
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
auth_login_url = EndpointConfig.resolve(DEFAULT_AUTH_LOGIN_URL, "/api/Auth/login")
|
||||||
|
auth_register_url = EndpointConfig.resolve(DEFAULT_AUTH_REGISTER_URL, "/api/Auth/register")
|
||||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
add_theme_constant_override("separation", 18)
|
add_theme_constant_override("separation", 18)
|
||||||
|
|
||||||
@@ -190,7 +195,7 @@ func _send_login(username: String, password: String, is_automatic: bool) -> void
|
|||||||
success_label.text = "Account created. Signing you in..."
|
success_label.text = "Account created. Signing you in..."
|
||||||
var payload := {"username": username, "password": password}
|
var payload := {"username": username, "password": password}
|
||||||
var headers := PackedStringArray(["Content-Type: application/json"])
|
var headers := PackedStringArray(["Content-Type: application/json"])
|
||||||
var request_error := login_request.request(AUTH_LOGIN_URL, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
|
var request_error := login_request.request(auth_login_url, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
|
||||||
if request_error != OK:
|
if request_error != OK:
|
||||||
_set_busy(false)
|
_set_busy(false)
|
||||||
login_button.text = "Log In"
|
login_button.text = "Log In"
|
||||||
@@ -254,7 +259,7 @@ func _on_register_pressed() -> void:
|
|||||||
_clear_messages()
|
_clear_messages()
|
||||||
var payload := {"username": username, "email": email, "password": password}
|
var payload := {"username": username, "email": email, "password": password}
|
||||||
var headers := PackedStringArray(["Content-Type: application/json"])
|
var headers := PackedStringArray(["Content-Type: application/json"])
|
||||||
var request_error := register_request.request(AUTH_REGISTER_URL, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
|
var request_error := register_request.request(auth_register_url, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
|
||||||
if request_error != OK:
|
if request_error != OK:
|
||||||
_set_busy(false)
|
_set_busy(false)
|
||||||
register_button.text = "Create Account"
|
register_button.text = "Create Account"
|
||||||
|
|||||||
@@ -14,9 +14,21 @@ var current_screen_name := ""
|
|||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
Style.apply_app_theme(self)
|
Style.apply_app_theme(self)
|
||||||
_build_ui()
|
_build_ui()
|
||||||
|
resized.connect(_update_responsive_layout)
|
||||||
|
_update_responsive_layout()
|
||||||
show_screen("selection" if AuthState.is_logged_in else "login", "exam")
|
show_screen("selection" if AuthState.is_logged_in else "login", "exam")
|
||||||
|
|
||||||
|
|
||||||
|
func _update_responsive_layout() -> void:
|
||||||
|
if header_stats != null:
|
||||||
|
header_stats.visible = size.x >= 900.0
|
||||||
|
if app_menu != null:
|
||||||
|
app_menu.custom_minimum_size.x = minf(340.0, maxf(280.0, size.x - 32.0))
|
||||||
|
if app_menu.visible:
|
||||||
|
app_menu.size = app_menu.custom_minimum_size
|
||||||
|
app_menu.position = (get_viewport_rect().size - app_menu.size) * 0.5
|
||||||
|
|
||||||
|
|
||||||
func show_screen(screen_name: String, payload: Variant = null) -> void:
|
func show_screen(screen_name: String, payload: Variant = null) -> void:
|
||||||
_close_app_menu()
|
_close_app_menu()
|
||||||
for child in $Root/Screens.get_children():
|
for child in $Root/Screens.get_children():
|
||||||
|
|||||||
@@ -357,6 +357,20 @@ func _ready() -> void:
|
|||||||
|
|
||||||
_build_labs_modal()
|
_build_labs_modal()
|
||||||
_build_report_modal()
|
_build_report_modal()
|
||||||
|
resized.connect(_fit_modal_panels)
|
||||||
|
_fit_modal_panels()
|
||||||
|
|
||||||
|
|
||||||
|
func _fit_modal_panels() -> void:
|
||||||
|
var available := get_viewport_rect().size - Vector2(32, 32)
|
||||||
|
if report_panel != null:
|
||||||
|
report_panel.custom_minimum_size = Vector2(
|
||||||
|
minf(700.0, maxf(320.0, available.x)),
|
||||||
|
minf(600.0, maxf(560.0, available.y)))
|
||||||
|
if labs_panel != null:
|
||||||
|
labs_panel.custom_minimum_size = Vector2(
|
||||||
|
minf(760.0, maxf(320.0, available.x)),
|
||||||
|
minf(430.0, maxf(360.0, available.y)))
|
||||||
|
|
||||||
|
|
||||||
func on_show(_payload: Variant = null) -> void:
|
func on_show(_payload: Variant = null) -> void:
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
FROM ubuntu:24.04 AS exporter
|
||||||
|
|
||||||
|
ARG GODOT_VERSION=4.6.2
|
||||||
|
ARG GODOT_RELEASE=${GODOT_VERSION}-stable
|
||||||
|
ARG GODOT_TEMPLATE_DIR=${GODOT_VERSION}.stable
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates curl unzip libasound2t64 libfontconfig1 libgl1 libx11-6 \
|
||||||
|
libxcursor1 libxi6 libxinerama1 libxrandr2 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /opt/godot
|
||||||
|
RUN curl -fsSLO "https://github.com/godotengine/godot-builds/releases/download/${GODOT_RELEASE}/Godot_v${GODOT_RELEASE}_linux.x86_64.zip" \
|
||||||
|
&& curl -fsSLO "https://github.com/godotengine/godot-builds/releases/download/${GODOT_RELEASE}/Godot_v${GODOT_RELEASE}_export_templates.tpz" \
|
||||||
|
&& curl -fsSLO "https://github.com/godotengine/godot-builds/releases/download/${GODOT_RELEASE}/SHA512-SUMS.txt" \
|
||||||
|
&& grep "Godot_v${GODOT_RELEASE}_linux.x86_64.zip\|Godot_v${GODOT_RELEASE}_export_templates.tpz" SHA512-SUMS.txt > selected-checksums.txt \
|
||||||
|
&& sha512sum --check selected-checksums.txt \
|
||||||
|
&& unzip -q "Godot_v${GODOT_RELEASE}_linux.x86_64.zip" \
|
||||||
|
&& install -m 0755 "Godot_v${GODOT_RELEASE}_linux.x86_64" /usr/local/bin/godot \
|
||||||
|
&& mkdir -p "/root/.local/share/godot/export_templates/${GODOT_TEMPLATE_DIR}" /tmp/godot-templates \
|
||||||
|
&& unzip -q "Godot_v${GODOT_RELEASE}_export_templates.tpz" -d /tmp/godot-templates \
|
||||||
|
&& cp -a /tmp/godot-templates/templates/. "/root/.local/share/godot/export_templates/${GODOT_TEMPLATE_DIR}/"
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
RUN mkdir -p /build \
|
||||||
|
&& godot --headless --editor --path /src --quit \
|
||||||
|
&& godot --headless --path /src --export-release Web /build/index.html \
|
||||||
|
&& test -s /build/index.html \
|
||||||
|
&& test -s /build/index.wasm \
|
||||||
|
&& test -s /build/index.pck
|
||||||
|
|
||||||
|
FROM nginx:1.27-alpine
|
||||||
|
COPY web/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY --from=exporter /build/ /usr/share/nginx/html/
|
||||||
|
EXPOSE 8080
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD wget -q -O /dev/null http://127.0.0.1:8080/healthz || exit 1
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: step3-web
|
||||||
|
labels:
|
||||||
|
app: step3-web
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxUnavailable: 0
|
||||||
|
maxSurge: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: step3-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: step3-web
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: web
|
||||||
|
image: step3-web:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 128Mi
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 2
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 20
|
||||||
|
timeoutSeconds: 2
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: step3-web
|
||||||
|
labels:
|
||||||
|
app: step3-web
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: step3-web
|
||||||
|
type: NodePort
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
nodePort: 30089
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
gzip on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_types application/javascript application/wasm application/octet-stream text/css;
|
||||||
|
|
||||||
|
location = /healthz {
|
||||||
|
access_log off;
|
||||||
|
default_type text/plain;
|
||||||
|
return 200 "ok\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /index.html {
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
add_header Cache-Control "public, max-age=3600" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user