Installing Servatrice on Kubernetes - Cockatrice/Cockatrice GitHub Wiki
- A working kubernetes cluster with:
- Loadbalancer support
- Persistent volume support
- Cert-Manager
- kubectl
- helm
- source of caffeine
- modicum of patience
This document will serve as a set of general guidelines and kubernetes resource templates for bringing up your very own servatrice instance for you and your friends
While this document does not intend to provide guidance on how to operate and secure a kubernetes cluster, best efforts are made to ensure secure defaults are utilized. For the purposes of this document, I will be using a tool known as KIND to run a kubernetes cluster on my local machine using docker.
The steps are equally applicable to other kubernetes products such as:
Our servatrice server needs a mysql-compatible database for proper operation. We will utilize MariaDB managed by mariadb-operator.
An operator is a specialized program that takes all the Expert-level knowledge of an SME and packages it into a convenient form-factor thats always running to ensure our database is reconciled to its defined state.
Lets go ahead and install the operator into our cluster
helm upgrade --install mariadb-operator mariadb-operator \
--repo https://helm.mariadb.com/mariadb-operator \
--namespace mariadb-system \
--create-namespace \
--set webhook.cert.certManager.enabled=true \
--set certController.enabled=false \
--set crds.enabled=true
Explained:
-
webhook.cert.certManager.enabled=true
andcertController.enabled=false
are set so that some neccsary certificates are automatically generated during installation using cert-manager -
crds.enabled=true
is set to ensure the resource definitions (CRDs) for managing our MariaDB instance are loaded
Kubernetes groups things together using namespaces. Lets make a namespace to group all the resources for our servatrice server.
00-namespace.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: cockatrice
and apply it to our cluster:
kubectl apply -f 00-namespace.yaml
Next, let's define our database instance so the mariadb-operator can create it for us.
01a-mariadb.yaml
apiVersion: k8s.mariadb.com/v1alpha1
kind: MariaDB
metadata:
name: db-servatrice # name of our MariaDB Server
namespace: cockatrice # our namespace we defined in step 1
spec:
rootPasswordSecretKeyRef: # Generate MariaDB root password for operator management
name: root-servatrice
key: password
generate: true
username: servatrice # MariaDB username
passwordSecretKeyRef: # Generate MariaDB password for username
name: user-servatrice-password
key: password
generate: true
database: servatrice # name of initial database to create on the server
port: 3306 # Port to listen on - Default: 3306
storage:
size: 4Gi # Size of Persistent volume allocated to hold the database files. Adjust accordingly.
####
# NOTE: adjust innodb_buffer_pool_size to 80%-100% of memory limits for better performance
####
myCnf: |
[mariadb]
bind-address=*
default_storage_engine=InnoDB
binlog_format=row
innodb_autoinc_lock_mode=2
innodb_buffer_pool_size=410M
max_allowed_packet=256M
resources:
requests:
cpu: 100m # starting cpu request for the Database server process
memory: 128Mi # starting memory request for the Database server process
limits:
cpu: 300m # top-end cpu limit for the Database server process
memory: 512Mi # top-end memory limit for the Database server process
metrics:
enabled: false # Disable metrics unless you have prometheus configured in your cluster
and apply it to our cluster:
kubectl apply -f 01a-mariadb.yaml
Now we have a fresh new MariaDB server, assigned user, and database but no tables!
Let's fix that. We can create a sqljob to seed the database and accompanying configmap holding the initial .sql file
01c-sqljob.yaml
apiVersion: k8s.mariadb.com/v1alpha1
kind: SqlJob
metadata:
name: servatrice-create-table
namespace: cockatrice
spec:
database: servatrice
username: servatrice
passwordSecretKeyRef:
name: user-servatrice-password
key: password
mariaDbRef:
name: db-servatrice
sqlConfigMapKeyRef:
name: servatrice-script
key: 'servatrice.sql'
and
01b-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: servatrice-script
namespace: cockatrice
data:
servatrice.sql: |
-- Schema file for servatrice database.
-- This schema file is using the default table prefix "cockatrice",
-- to match the "prefix=cockatrice" default setting in servatrice.ini
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
-- Every time the database schema changes, the schema version number
-- must be incremented. Also remember to update the corresponding
-- number in servatrice/src/servatrice_database_interface.h
CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` (
`version` int(7) unsigned NOT NULL,
PRIMARY KEY (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
INSERT INTO cockatrice_schema_version VALUES(29);
-- users and user data tables
CREATE TABLE IF NOT EXISTS `cockatrice_users` (
`id` int(7) unsigned zerofill NOT NULL auto_increment,
`admin` tinyint(1) NOT NULL,
`name` varchar(35) NOT NULL,
`realname` varchar(255) NOT NULL,
`password_sha512` char(120) NOT NULL,
`email` varchar(255) NOT NULL,
`country` char(2) NOT NULL,
`avatar_bmp` mediumblob NOT NULL,
`registrationDate` datetime NOT NULL,
`active` tinyint(1) NOT NULL,
`token` binary(16),
`clientid` varchar(15) NOT NULL,
`privlevel` enum("NONE","VIP","DONATOR") NOT NULL,
`privlevelStartDate` datetime NOT NULL,
`privlevelEndDate` datetime NOT NULL,
`passwordLastChangedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
KEY `token` (`token`),
KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` (
`id` int(7) unsigned zerofill NOT NULL auto_increment,
`id_folder` int(7) unsigned zerofill NOT NULL,
`id_user` int(7) unsigned NULL,
`name` varchar(50) NOT NULL,
`upload_time` datetime NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`),
KEY `FolderPlusUser` (`id_folder`,`id_user`),
FOREIGN KEY(`id_user`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_decklist_folders` (
`id` int(7) unsigned zerofill NOT NULL auto_increment,
`id_parent` int(7) unsigned zerofill NOT NULL,
`id_user` int(7) unsigned NULL,
`name` varchar(30) NOT NULL,
PRIMARY KEY (`id`),
KEY `ParentPlusUser` (`id_parent`,`id_user`),
FOREIGN KEY(`id_user`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_ignorelist` (
`id_user1` int(7) unsigned NOT NULL,
`id_user2` int(7) unsigned NOT NULL,
UNIQUE KEY `key` (`id_user1`, `id_user2`),
FOREIGN KEY(`id_user1`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(`id_user2`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_buddylist` (
`id_user1` int(7) unsigned NOT NULL,
`id_user2` int(7) unsigned NOT NULL,
UNIQUE KEY `key` (`id_user1`, `id_user2`),
FOREIGN KEY(`id_user1`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(`id_user2`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
-- rooms
CREATE TABLE IF NOT EXISTS `cockatrice_rooms` (
`id` int(7) unsigned NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
`descr` varchar(255) NOT NULL,
`permissionlevel` enum('NONE','REGISTERED','MODERATOR','ADMINISTRATOR') NOT NULL,
`privlevel` enum('NONE','PRIVILEGED','VIP','DONATOR') NOT NULL,
`auto_join` tinyint(1) default 0,
`join_message` varchar(255) NOT NULL,
`chat_history_size` int(4) NOT NULL,
`id_server` tinyint(3) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_rooms_gametypes` (
`id_room` int(7) unsigned NOT NULL,
`name` varchar(50) NOT NULL,
`id_server` tinyint(3) NOT NULL DEFAULT 1,
FOREIGN KEY(`id_room`) REFERENCES `cockatrice_rooms`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
-- games
CREATE TABLE IF NOT EXISTS `cockatrice_games` (
`room_name` varchar(255) NOT NULL,
`id` int(7) unsigned NOT NULL auto_increment,
`descr` varchar(50) default NULL,
`creator_name` varchar(35) NOT NULL,
`password` tinyint(1) NOT NULL,
`game_types` varchar(255) NOT NULL,
`player_count` tinyint(3) NOT NULL,
`time_started` datetime default NULL,
`time_finished` datetime default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_games_players` (
`id_game` int(7) unsigned zerofill NOT NULL,
`player_name` varchar(35) NOT NULL,
FOREIGN KEY(`id_game`) REFERENCES `cockatrice_games`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
-- Note: an empty row with id_game = NULL is created when the game is created,
-- and then updated when the game ends with the full replay data.
CREATE TABLE IF NOT EXISTS `cockatrice_replays` (
`id` int(7) NOT NULL AUTO_INCREMENT,
`id_game` int(7) unsigned NULL,
`duration` int(7) NOT NULL,
`replay` mediumblob NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY(`id_game`) REFERENCES `cockatrice_games`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_replays_access` (
`id_game` int(7) unsigned NOT NULL,
`id_player` int(7) unsigned NOT NULL,
`replay_name` varchar(255) NOT NULL,
`do_not_hide` tinyint(1) NOT NULL,
KEY `id_player` (`id_player`),
FOREIGN KEY(`id_game`) REFERENCES `cockatrice_games`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(`id_player`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
-- server administration
-- Note: unused table
CREATE TABLE IF NOT EXISTS `cockatrice_servers` (
`id` mediumint(8) unsigned NOT NULL,
`ssl_cert` text NOT NULL,
`hostname` varchar(255) NOT NULL,
`address` varchar(255) NOT NULL,
`game_port` mediumint(8) unsigned NOT NULL,
`control_port` mediumint(9) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_uptime` (
`id_server` tinyint(3) NOT NULL,
`timest` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`uptime` int(11) NOT NULL,
`users_count` int(11) NOT NULL,
`mods_count` int(11) NOT NULL DEFAULT 0,
`mods_list` TEXT,
`games_count` int(11) NOT NULL,
`rx_bytes` int(11) NOT NULL,
`tx_bytes` int(11) NOT NULL,
PRIMARY KEY (`timest`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_servermessages` (
`id_server` tinyint(3) not null default 1,
`timest` datetime NOT NULL default '0000-00-00 00:00:00',
`message` text,
PRIMARY KEY (`timest`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_sessions` (
`id` int(9) NOT NULL AUTO_INCREMENT,
`user_name` varchar(35) NOT NULL,
`id_server` tinyint(3) NOT NULL,
`ip_address` varchar(45) NOT NULL,
`start_time` datetime NOT NULL,
`end_time` datetime DEFAULT NULL,
`clientid` varchar(15) NOT NULL,
`connection_type` ENUM('tcp', 'websocket'),
PRIMARY KEY (`id`),
KEY `username` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
-- server moderation
CREATE TABLE IF NOT EXISTS `cockatrice_bans` (
`user_name` varchar(35) NOT NULL,
`ip_address` varchar(45) NOT NULL,
`id_admin` int(7) unsigned zerofill NOT NULL,
`time_from` datetime NOT NULL,
`minutes` int(6) NOT NULL,
`reason` text NOT NULL,
`visible_reason` text NOT NULL,
`clientid` varchar(15) NOT NULL,
PRIMARY KEY (`user_name`,`time_from`),
KEY `time_from` (`time_from`,`ip_address`),
KEY `ip_address` (`ip_address`),
FOREIGN KEY(`id_admin`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_warnings` (
`user_id` int(7) unsigned NOT NULL,
`user_name` varchar(35) NOT NULL,
`mod_name` varchar(35) NOT NULL,
`reason` text NOT NULL,
`time_of` datetime NOT NULL,
`clientid` varchar(15) NOT NULL,
PRIMARY KEY (`user_id`,`time_of`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_log` (
`log_time` datetime NOT NULL,
`sender_id` int(7) unsigned NULL,
`sender_name` varchar(35) NOT NULL,
`sender_ip` varchar(45) NOT NULL,
`log_message` text NOT NULL,
`target_type` ENUM('room', 'game', 'chat'),
`target_id` int(7) NULL,
`target_name` varchar(50) NOT NULL,
KEY `sender_name` (`sender_name`),
KEY `sender_ip` (`sender_ip`),
KEY `target_type` (`target_type`),
KEY `target_id` (`target_id`),
KEY `target_name` (`target_name`),
FOREIGN KEY(`sender_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
-- No FK on target_id, it can be zero
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_activation_emails` (
`name` varchar(35) NOT NULL,
FOREIGN KEY(`name`) REFERENCES `cockatrice_users`(`name`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_user_analytics` (
`id` int(7) unsigned zerofill NOT NULL,
`client_ver` varchar(35) NOT NULL,
`last_login` datetime NOT NULL,
`notes` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY(`id`) REFERENCES `cockatrice_users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_donations` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(35) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`payment_pre_fee` double DEFAULT NULL,
`payment_post_fee` double DEFAULT NULL,
`term_length` int(11) DEFAULT NULL,
`date` varchar(255) DEFAULT NULL,
`pp_type` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_forgot_password` (
`id` int(7) unsigned zerofill NOT NULL auto_increment,
`name` varchar(35) NOT NULL,
`requestDate` datetime NOT NULL default '0000-00-00 00:00:00',
`emailed` tinyint(1) NOT NULL default 0,
PRIMARY KEY (`id`),
KEY `user_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `cockatrice_audit` (
`id` int(7) unsigned zerofill NOT NULL auto_increment,
`id_server` tinyint(3) NOT NULL,
`name` varchar(35) NOT NULL,
`ip_address` varchar(45) NOT NULL,
`clientid` varchar(15) NOT NULL,
`incidentDate` datetime NOT NULL default '0000-00-00 00:00:00',
`action` varchar(35) NOT NULL,
`results` ENUM('fail', 'success') NOT NULL DEFAULT 'fail',
`details` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `user_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
and apply them to our cluster:
kubectl apply -f 01b-configmap.yaml
kubectl apply -f 01c-sqljob.yaml
Servatrice can accept a database configuration in two ways:
- via the configuration file
- via the environment variable
DATABASE_URL
We will be using the second method as to alleviate us from having to programmatically modify the configuration file for EVERY new server we want to setup.
We can use a Connection resourse to get a properly formatted database connection string
01d-connection.yaml
apiVersion: k8s.mariadb.com/v1alpha1
kind: Connection
metadata:
name: connection
namespace: cockatrice
spec:
mariaDbRef:
name: db-servatrice
username: servatrice
passwordSecretKeyRef:
name: user-servatrice-password
key: password
database: servatrice
secretName: connection-servatrice
secretTemplate:
metadata:
labels:
k8s.mariadb.com/connection: connection-servatrice
annotations:
k8s.mariadb.com/connection: connection-servatrice
key: dsn
format: mysql://{{ .Username }}:{{ .Password }}@{{ .Host }}:{{ .Port }}/{{ .Database }}{{ .Params }}
usernameKey: username
passwordKey: password
hostKey: host
portKey: port
databaseKey: database
healthCheck:
interval: 30s
retryInterval: 3s
serviceName: db-servatrice
and apply to our cluster:
kubectl apply -f 01d-connection.yaml
Our servatrice server is going to need a configuration file. I will demonstrate a very basic one here that enables the following features:
Since we are connecting using DATABASE_URL
, servatrice also assumes the cockatrice table prefix as well as sql mode for authentication and room creation.
Our configuration file will mirror these facts even if those sections are effectively ignored
02-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: servatrice-config
namespace: cockatrice
data:
servatrice.ini: |
[server]
name="Generic Cockatrice Server"
web_socket_ip_header="X-Real-IP"
id=1
[authentication]
method=sql
[users]
minnamelength=4
maxnamelength=24
[registration]
enabled=true
requireemail=false
requireemailactivation=false
[rooms]
method=sql
[game]
store_replays=false
and apply to our cluster:
kubectl apply -f 02-configmap.yaml
When servatrice operates in sql mode from rooms, it does NOT create a default room.
Lets pre-populate a General room with game modes for most played formats
03a-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: default-room-script
namespace: cockatrice
data:
initial_room_mtg.sql: |
insert into cockatrice_rooms (name,descr,permissionlevel,privlevel,auto_join,join_message,chat_history_size,id_server) values ('General Room','Play anything here!','NONE','NONE',1,'Welcome to the General Room.',100,1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Standard',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Pioneer',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Modern',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Legacy',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Vintage',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Limited',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Commander',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'CEDH',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Highlander',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Pre-Modern',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Old-School',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Pauper',1);
insert into cockatrice_rooms_gametypes (id_room,name,id_server) values (1,'Other',1);
03b-sqljob.yaml
apiVersion: k8s.mariadb.com/v1alpha1
kind: SqlJob
metadata:
name: servatrice-create-initial-room
namespace: cockatrice
spec:
database: servatrice
username: servatrice
passwordSecretKeyRef:
name: user-servatrice-password
key: password
mariaDbRef:
name: db-servatrice
sqlConfigMapKeyRef:
name: default-room-script
key: 'initial_room_mtg.sql'
and apply them to our cluster:
kubectl apply -f 03a-configmap.yaml
kubectl apply -f 03b-sqljob.yaml
Its finally time to deploy our server
I have built and published a docker image for Servatrice v2.9.0 using the Docker file from the Github Repo
docker.io/troopert/servatrice:v2.9.0-rc2
04-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: servatrice
namespace: cockatrice
spec:
selector:
matchLabels:
app: servatrice
role: server
template:
metadata:
labels:
app: servatrice
role: server
spec:
containers:
- name: server
image: docker.io/troopert/servatrice:v2.9.0-rc2
command: ["servatrice", "--log-to-console", "--config", "/etc/config/servatrice.ini"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: connection-servatrice
key: dsn
resources:
limits:
memory: "512Mi"
cpu: "1000m"
ports:
- containerPort: 4748
name: websocket
protocol: TCP
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: servatrice-config
and apply to our cluster:
kubectl apply -f 04-deployment.yaml
Kubernetes utilizes a Service
resource to direct traffic to our running copy of servatrice.
05-service.yaml
apiVersion: v1
kind: Service
metadata:
name: servatrice
namespace: cockatrice
spec:
selector:
app: servatrice
role: server
ports:
- port: 4748
name: websocket
targetPort: websocket
and apply to our cluster:
kubectl apply -f 05-service.yaml
The final piece to our puzzle is going to be an Ingress
resource. This is what will expose our servatrice instance to the outside world and let us enjoy with friends.
This MAY require some amount of tweaking depending on the the ingress controller your kubernetes cluster utilizes as servatrice will be exposed behind a TLS-encrypted websocket.
As-IS this is written with a few pre-conceived notions:
- The in-use ingress controller is kubernetes official ingress-nginx
- cert-manager is configured with a ClusterIssuer named
letsencrypt-production
06-ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-servatrice
namespace: cockatrice
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-production"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/server-snippets: |
location /servatrice {
proxy_set_header Upgrade $http_upgrade;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header Connection "upgrade";
proxy_cache_bypass $http_upgrade;
}
spec:
tls:
- hosts:
- mtg.example.com # Update this to a DNS hostname/domain you control
secretName: tls-host-mtg
rules:
- host: mtg.example.com # Update this to a DNS hostname/domain you control
http:
paths:
- path: /servatrice
pathType: Prefix
backend:
service:
name: servatrice
port:
name: websocket
and apply to our cluster:
kubectl apply -f 06-ingress.yaml
If all works properly, your server should be issued an SSL certificate via cert-manager within a few minutes
You should now have a running servatrice instance hosted on your kubernetes cluster for you and your friends to enjoy.
Open Cockatrice, register to server with a username and password (no email required) and have tons of fun!!!