1 unstable release
0.1.0 | Oct 1, 2023 |
---|
#745 in HTTP server
87KB
2K
SLoC
Launcho - Ultra-simplified k8s replacement in 2k lines of Rust
I got frustrated with the complexity of k8s, so I'm writing the dumbest replacement I can. All this program does is:
- Relaunch processes that die
- Perform health checks, and relaunch processes that fail
- Spool logs, for remote access (like with
kubectl get logs
) - Allow remote reconfiguration, including upgrading processes
- Manage load balancing (including sunsetting processes that are upgrading, and not sending traffic to new processes until they pass a health check)
This is all written in ~2k lines of Rust.
Setting up launcho
cargo install launcho
On the server do:
sudo apt-get install ipvsadm # Make sure you have ipvsadm installed
sudo launcho server
Also feel free to add launcho server
to init.d
or whatever to make it run on start-up.
Once a server is running you can run launcho print-auth
on the server to get the auth info needed for connecting. It'll look something like:
# Paste this into ~/.launcho/launcho-client-auth.yaml on the client machine
host: change-me-to-point-to-the-server.example.com:12888
cert: |
-----BEGIN CERTIFICATE-----
MIIBWzCCAQKgAwIBAgIUY2V0NJXiRC+qMdydF42rmIR6TfIwCgYIKoZIzj0EAwIw
ITEfMB0GA1UEAwwWcmNnZW4gc2VsZiBzaWduZWQgY2VydDAgFw03NTAxMDEwMDAw
MDBaGA80MDk2MDEwMTAwMDAwMFowITEfMB0GA1UEAwwWcmNnZW4gc2VsZiBzaWdu
ZWQgY2VydDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBNEBkWsYqN/EDudl0mE
f2cLr1iWbGMB7YmoxmVy+VMzAZ1WvKO23kenPNKNHZC9vomNLww7HtRHDau4GmXd
+J6jFjAUMBIGA1UdEQQLMAmCB2xhdW5jaG8wCgYIKoZIzj0EAwIDRwAwRAIgSd8V
Q7j0xX/zGmyiaAToDXMzo/3pjmZ4WtLUg4ROfTYCIFV1Kjw1lHObS0HPVUkc8UKq
Kul0XMy4//sfCpqT1SeD
-----END CERTIFICATE-----
private: null
token: 4d0f71f5d2d69b25b6a1638245386ebb1b3f6f4cc109006907707fa6e30bedd8
You then paste this into ~/.launcho/launcho-client-auth.yaml
on the client machine, after first changing host
to point to the server.
Finally, you can control the launcho server. You should be able to see something like:
$ launcho status
Events:
Warning { msg: "Auth file not found at \"/root/.launcho/launcho-server-auth.yaml\" -- generating a new one" }
Using launcho
The main idea is that launcho has a "target" configuration that it is attempting to reach. This target is specified via yaml that you can version control. To get the current target:
launcho target get # or launcho t get
To set the current target:
launcho target set FILE # or launcho t set FILE
The default target file will look like:
# No orchestration target set, this is an example file.
# Use `launcho target get` and `launcho target set` to edit this.
# Create processes like this:
processes:
# -
# name: "example_proc"
# command: ["python", "server.py"]
# env:
# # Use ${SECRET_NAME} to access secrets defined in the server config.
# DATABASE_URL: "${DATABASE_URL}"
# # List all services this process should receive traffic from.
# # Each service for each process gets allocated a port, which is given
# # via a corresponding environment variable, in this case SERVICE_PORT_WEB.
# receives:
# - "web"
# # Define an endpoint to hit to check for health.
# health:
# service: "web"
# path: "/health"
# #uid: "whoever"
# #gid: "whoever"
# #cwd: "/var/wherever"
# Create services like this:
services:
# -
# name: "web"
# on: "127.0.0.1:5000"
There are several concepts here:
Name | Meaning |
---|---|
target |
The configuration of processes + services that launcho is trying to keep running |
process |
A single process that launcho is trying to keep running (basically a k8s pod) |
service |
Each "service" load balances traffic over some set of processes that receive it (basically a k8s service) |
secret |
Launcho maintains a key-value store mapping secrets to strings (basically a k8s secret) |
resource |
Launcho stores blobs of data that processes can access (used like k8s container images) |
Each service will be routed to every process that receives it.
Each process will get an environment variable with a name like SERVICE_PORT_SERVICE_NAME
for every service it receives -- your processes should bind to localhost:that service port
in order to receive their load-balanced share of the service requests.
You can list/upload/download/delete resources with:
launcho resource ls # or launcho r ls
launcho resource up FILE # ... and so on
launcho resource down RESOURCE_ID OUTPUT_FILE
launcho resource rm RESOURCE_ID RESOURCE_ID...
You can modify secrets with:
launcho secret ls # or launcho s ls
launcho secret get SECRET_NAME SECRET_NAME...
launcho secret set SECRET_NAME VALUE
launcho secret rm SECRET_NAME SECRET_NAME...
Note that modifying a secret will automatically launch new versions of any processes whose configs depend on it, and traffic will be moved over once the new versions are healthy.
Intended workflow
A process may request some resources be placed in its working directory, and you can run a command before the process is started. This is the intended mechanism for making what are basically "container images". For example, using the following server:
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
const path = url.parse(req.url).pathname;
if (path === '/health') {
res.statusCode = 200;
res.end('OK');
} else if (path === '/') {
res.statusCode = 200;
res.end('Hello, world!');
} else {
res.statusCode = 404;
res.end('Not Found');
}
});
const port = process.env.SERVICE_PORT_TRAFFIC;
server.listen(port, () => console.log(`Server running on port ${port}`));
we could set the following target (using launcho target set target.yaml
):
processes:
-
name: "main_server"
resources:
-
id: "${BUNDLE_RESOURCE_ID}"
file: "bundle.tar.bz2"
before: |
tar -xf bundle.tar.bz2
mv bundle/* .
command: ["node", "server.js"]
receives:
- "traffic"
health:
service: "traffic"
path: "/health"
services:
-
name: "traffic"
on: "127.0.0.1:5000"
Then to deploy a new version you need merely do:
# Assuming bundle/server.js contains the above server...
tar -cvvhjf bundle.tar.bz2 bundle/
launcho resource up bundle.tar.bz2 | tee NEW_RESOURCE_ID
launcho secret set BUNDLE_RESOURCE_ID $(cat NEW_RESOURCE_ID)
This would cause a new version of the server to launch. All traffic to port 5000 will then be rerouted from the old version to the new version once the new version passes a health check, and then the old version will be killed.
You can check up on the server with launcho status
, and get its logs via launcho logs PROCESS_RANDOM_NAME
.
The assumption is that generally you'll point some sort of TLS-handling reverse proxy at your services (for example, maybe you point nginx at 127.0.0.1:5000, and leave nginx outside of the purview of launcho, but that's not mandatory, of course).
Dependencies
~27–43MB
~807K SLoC