mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03: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
|
||||
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.
|
||||
|
||||
## 🎬 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.
|
||||
* **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:
|
||||
|
||||

|
||||
@@ -120,8 +114,10 @@ View the [user guide](docs/user_guide.md) for more information.
|
||||
|
||||
## ⚙️ How it works
|
||||
|
||||
Check out the [design document](docs/design.md) to understand Uncloud's design philosophy and goals. Here, let's peek
|
||||
under the hood to see what happens when you run certain commands.
|
||||
Check out the [design document](docs/design.md) to understand Uncloud's design philosophy and goals.
|
||||
|
||||
<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:**
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
</details>
|
||||
|
||||
## 🏗 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 {
|
||||
if _, err := clusterClient.GetDomain(ctx); err != nil {
|
||||
domain, err := clusterClient.GetDomain(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, api.ErrNotFound) {
|
||||
fmt.Println("Skipping DNS records update as no cluster domain is reserved (see 'uc dns').")
|
||||
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.
|
||||
|
||||
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
|
||||
records, err = clusterClient.CreateIngressRecords(ctx, client.CaddyServiceName)
|
||||
return err
|
||||
@@ -172,18 +173,19 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
||||
if err != nil {
|
||||
if errors.Is(err, client.ErrNoReachableMachines) {
|
||||
fmt.Println()
|
||||
fmt.Println("DNS records could not be updated as there are no internet-reachable machines running " +
|
||||
"caddy containers.")
|
||||
fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+
|
||||
"machines running caddy containers.\n", domain)
|
||||
fmt.Println()
|
||||
fmt.Println("Possible solutions:")
|
||||
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("- Check firewall settings on your machines")
|
||||
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("Your services will not be accessible from the internet until at least one machine " +
|
||||
"becomes reachable.")
|
||||
fmt.Println("Your services won't be accessible from the internet until at least one machine " +
|
||||
"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)
|
||||
}
|
||||
|
||||
+11
-2
@@ -31,6 +31,7 @@ const (
|
||||
)
|
||||
|
||||
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 {
|
||||
return serviceIDRegexp.MatchString(id)
|
||||
@@ -103,8 +104,15 @@ func (s *ServiceSpec) Validate() error {
|
||||
default:
|
||||
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 {
|
||||
if (p.Mode == "" || p.Mode == PortModeIngress) &&
|
||||
@@ -138,6 +146,7 @@ func (s *ServiceSpec) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (s *ServiceSpec) Clone() ServiceSpec {
|
||||
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
|
||||
// version of the official Caddy Docker image is used.
|
||||
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 == "" {
|
||||
latest, err := LatestCaddyImage()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("look up latest Caddy image: %w", err)
|
||||
}
|
||||
|
||||
image = reference.FamiliarString(latest)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user