mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e35e69b4be | ||
|
|
11c2e641f1 | ||
|
|
bb97537df7 | ||
|
|
9f4b8179b5 | ||
|
|
f7b079c4f2 | ||
|
|
469d6fadcd | ||
|
|
5f214247ca |
@@ -13,7 +13,7 @@ Unlike traditional orchestrators, there's no central control plane and quorum to
|
|||||||
synchronized copy of the cluster state through peer-to-peer communication, keeping cluster operations functional even if
|
synchronized copy of the cluster state through peer-to-peer communication, keeping cluster operations functional even if
|
||||||
some machines go offline.
|
some machines go offline.
|
||||||
|
|
||||||
Uncloud aims to be the solution for developers who want the flexibility of self-hosted infrastructure without the
|
Uncloud is the solution for developers who want the flexibility of self-hosted infrastructure without the
|
||||||
operational complexity of Kubernetes.
|
operational complexity of Kubernetes.
|
||||||
|
|
||||||
## 🎬 Quick demo
|
## 🎬 Quick demo
|
||||||
@@ -46,12 +46,6 @@ Deploy a highly available web app with automatic HTTPS across multiple regions a
|
|||||||
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
||||||
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
||||||
|
|
||||||
Coming soon:
|
|
||||||
|
|
||||||
* Project isolation through environments/namespaces
|
|
||||||
* Configs and secrets management using providers such as HashiCorp Vault and 1Password
|
|
||||||
* Monitoring and log aggregation
|
|
||||||
|
|
||||||
Here is a diagram of an Uncloud multi-provider cluster of 3 machines:
|
Here is a diagram of an Uncloud multi-provider cluster of 3 machines:
|
||||||
|
|
||||||

|

|
||||||
@@ -120,8 +114,10 @@ View the [user guide](docs/user_guide.md) for more information.
|
|||||||
|
|
||||||
## ⚙️ How it works
|
## ⚙️ How it works
|
||||||
|
|
||||||
Check out the [design document](docs/design.md) to understand Uncloud's design philosophy and goals. Here, let's peek
|
Check out the [design document](docs/design.md) to understand Uncloud's design philosophy and goals.
|
||||||
under the hood to see what happens when you run certain commands.
|
|
||||||
|
<details>
|
||||||
|
<summary>Here, let's peek under the hood to see what happens when you run certain commands.</summary>
|
||||||
|
|
||||||
**When you initialize a new cluster on a machine:**
|
**When you initialize a new cluster on a machine:**
|
||||||
|
|
||||||
@@ -256,6 +252,7 @@ Look ma, no control plane or master nodes to maintain! Just a simple overlay net
|
|||||||
sync that lets machines work together. Want to check on things or make changes? Connect to any machine either implicitly
|
sync that lets machines work together. Want to check on things or make changes? Connect to any machine either implicitly
|
||||||
using the CLI or directly over SSH. They all have the complete cluster state and can control everything. It's like each
|
using the CLI or directly over SSH. They all have the complete cluster state and can control everything. It's like each
|
||||||
machine is a full backup of your control plane.
|
machine is a full backup of your control plane.
|
||||||
|
</details>
|
||||||
|
|
||||||
## 🏗 Project status
|
## 🏗 Project status
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,8 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, progressOut *streams.Out) error {
|
func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, progressOut *streams.Out) error {
|
||||||
if _, err := clusterClient.GetDomain(ctx); err != nil {
|
domain, err := clusterClient.GetDomain(ctx)
|
||||||
|
if err != nil {
|
||||||
if errors.Is(err, api.ErrNotFound) {
|
if errors.Is(err, api.ErrNotFound) {
|
||||||
fmt.Println("Skipping DNS records update as no cluster domain is reserved (see 'uc dns').")
|
fmt.Println("Skipping DNS records update as no cluster domain is reserved (see 'uc dns').")
|
||||||
return nil
|
return nil
|
||||||
@@ -164,7 +165,7 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
|||||||
// TODO: split the method into two: one to get the records and one to update them to ask for update confirmation.
|
// TODO: split the method into two: one to get the records and one to update them to ask for update confirmation.
|
||||||
|
|
||||||
var records []*pb.DNSRecord
|
var records []*pb.DNSRecord
|
||||||
err := progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
var err error
|
var err error
|
||||||
records, err = clusterClient.CreateIngressRecords(ctx, client.CaddyServiceName)
|
records, err = clusterClient.CreateIngressRecords(ctx, client.CaddyServiceName)
|
||||||
return err
|
return err
|
||||||
@@ -172,18 +173,19 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, client.ErrNoReachableMachines) {
|
if errors.Is(err, client.ErrNoReachableMachines) {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("DNS records could not be updated as there are no internet-reachable machines running " +
|
fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+
|
||||||
"caddy containers.")
|
"machines running caddy containers.\n", domain)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Possible solutions:")
|
fmt.Println("Possible solutions:")
|
||||||
fmt.Println("- Ensure your machines have public IP addresses")
|
fmt.Println("- Ensure your machines have public IP addresses")
|
||||||
fmt.Println("- Use --public-ip flag when adding machines to override the automatically detected IPs")
|
fmt.Println("- Use --public-ip flag when adding machines to override the automatically detected IPs")
|
||||||
fmt.Println("- Check firewall settings on your machines")
|
fmt.Println("- Check firewall settings on your machines")
|
||||||
fmt.Println("- Configure port forwarding if behind NAT")
|
fmt.Println("- Configure port forwarding if behind NAT")
|
||||||
fmt.Println("- Retry Caddy deployment after resolving connectivity issues with 'uc caddy deploy'")
|
fmt.Println("- Retry Caddy deployment with 'uc caddy deploy' after resolving connectivity issues")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Your services will not be accessible from the internet until at least one machine " +
|
fmt.Println("Your services won't be accessible from the internet until at least one machine " +
|
||||||
"becomes reachable.")
|
"becomes reachable. If you aren't planning to expose any services publicly, you can release " +
|
||||||
|
"the domain by running 'uc dns release'.")
|
||||||
}
|
}
|
||||||
return fmt.Errorf("failed to update DNS records pointing to caddy service: %w", err)
|
return fmt.Errorf("failed to update DNS records pointing to caddy service: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -31,6 +31,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var serviceIDRegexp = regexp.MustCompile("^[0-9a-f]{32}$")
|
var serviceIDRegexp = regexp.MustCompile("^[0-9a-f]{32}$")
|
||||||
|
var dnsLabelRegexp = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`)
|
||||||
|
|
||||||
func ValidateServiceID(id string) bool {
|
func ValidateServiceID(id string) bool {
|
||||||
return serviceIDRegexp.MatchString(id)
|
return serviceIDRegexp.MatchString(id)
|
||||||
@@ -104,7 +105,14 @@ func (s *ServiceSpec) Validate() error {
|
|||||||
return fmt.Errorf("invalid mode: %q", s.Mode)
|
return fmt.Errorf("invalid mode: %q", s.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: validate the service name is a valid DNS label.
|
if s.Name != "" {
|
||||||
|
if len(s.Name) > 63 {
|
||||||
|
return fmt.Errorf("service name too long (max 63 characters): %q", s.Name)
|
||||||
|
}
|
||||||
|
if !dnsLabelRegexp.MatchString(s.Name) {
|
||||||
|
return fmt.Errorf("invalid service name: %q. must be 1-63 characters, lowercase letters, numbers, and dashes only; must start and end with a letter or number", s.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range s.Ports {
|
for _, p := range s.Ports {
|
||||||
if (p.Mode == "" || p.Mode == PortModeIngress) &&
|
if (p.Mode == "" || p.Mode == PortModeIngress) &&
|
||||||
@@ -138,6 +146,7 @@ func (s *ServiceSpec) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (s *ServiceSpec) Clone() ServiceSpec {
|
func (s *ServiceSpec) Clone() ServiceSpec {
|
||||||
spec := *s
|
spec := *s
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -24,12 +24,12 @@ var caddyImageTagRegex = regexp.MustCompile(`^2\.\d+\.\d+$`)
|
|||||||
// The service is deployed in global mode to all machines in the cluster. If the image is not provided, the latest
|
// The service is deployed in global mode to all machines in the cluster. If the image is not provided, the latest
|
||||||
// version of the official Caddy Docker image is used.
|
// version of the official Caddy Docker image is used.
|
||||||
func (cli *Client) NewCaddyDeployment(image string, placement api.Placement) (*deploy.Deployment, error) {
|
func (cli *Client) NewCaddyDeployment(image string, placement api.Placement) (*deploy.Deployment, error) {
|
||||||
latest, err := LatestCaddyImage()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("look up latest Caddy image: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if image == "" {
|
if image == "" {
|
||||||
|
latest, err := LatestCaddyImage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("look up latest Caddy image: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
image = reference.FamiliarString(latest)
|
image = reference.FamiliarString(latest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user