🥉 k3s example working (#14)

* WIP commit - helm example

* 🥉 k3s working!
This commit is contained in:
Oscar Hermoso
2024-09-07 17:00:24 +08:00
committed by GitHub
parent 54dd00fa72
commit 7a978731bb
16 changed files with 272 additions and 72 deletions
+7 -1
View File
@@ -35,7 +35,13 @@ terraform apply
```sh
TF_ACC=1 go test -v ./...
TF_DEBUG=1 BINARYLANE_API_TOKEN=********* TF_ACC=1 go test -v ./...
```
### Update modules
```sh
go get -u && go mod tidy && go generate
```
### Adding resources/data sources to the provider
+31 -9
View File
@@ -1,6 +1,6 @@
# terraform-provider-binarylane
See the examples in the [examples directory](./examples/basic/main.tf).
See the examples in the [examples directory](./examples/basic/README.md).
```terraform
resource "binarylane_server" "example" {
@@ -10,15 +10,33 @@ resource "binarylane_server" "example" {
}
```
## WIP
## Progress
If somehow you use this in production I would be pretty impressed.
Planned features:
- [x] Create/delete a server when runing locally
- [x] Publish
- [x] [Terraform Registry](https://registry.terraform.io/providers/oscarhermoso/binarylane/latest/docs/resources/server)
- [x] OpenTofu
- [ ] Deploy the rest of the owl
- [x] Servers
- [x] Firewall Rules
- [x] Cloud Init
- [ ] Updating server properties
- [ ] Backups
- [ ] Software
- [ ] IPV6
- [ ] Alerts
- [x] SSH Keys
- [x] Virtual Private Cloud
- [ ] Images
- [ ] Load Balancers
- [ ] DNS
- [x] Docs
- [x] Generated docs
- [ ] Review & fill out remaining descriptions
- [ ] Include examples in docs
- [x] Examples
- [x] Basic
- [x] Cloud Init
- [ ] NixOS
- [ ] Virtual Private Cloud
- [ ] Kubernetes
## Parameters
@@ -78,7 +96,10 @@ jq '[ .regions[] | .slug ] | sort' tmp/regions.json
]
```
Sizes (not all sizes are available in all regions):
### Sizes
<details>
<summary>VM size</summary>
```sh
curl -X GET "https://api.binarylane.com.au/v2/sizes" \
@@ -86,6 +107,7 @@ curl -X GET "https://api.binarylane.com.au/v2/sizes" \
jq '[ .sizes[] | .slug ] | sort' tmp/sizes.json
```
</details>
```json
[
+1 -1
View File
@@ -11,7 +11,7 @@ provider "binarylane" {
}
resource "binarylane_server" "example" {
name = "tf-example"
name = "tf-example-basic"
region = "per" # or "syd", "mel", "bne", "sin"
image = "ubuntu-24.04"
size = "std-min" # 1 VPCU, 1 GB Memory, 20 GB NVME Storage, 1000 GB Data Transfer
+3
View File
@@ -35,3 +35,6 @@ override.tf.json
# Ignore CLI configuration files
.terraformrc
terraform.rc
# .kube/config contains credentials
.kube
+1
View File
@@ -5,3 +5,4 @@ This WIP example shows how to create a Kubernetes cluster on Binary Lane.
Prior art:
- [MartinHodges/create_k8s](https://github.com/MartinHodges/create_k8s) repo and the [medium article](https://medium.com/@martin.hodges/creating-a-kubernetes-cluster-from-scratch-in-1-hour-using-automation-a25e387be547) that goes with it.
- https://github.com/inscapist/terraform-k3s-private-cloud
- https://github.com/schnerring/schnerring.github.io/blob/07d06fb40e3d01f2483a739f516ac4d711a5742c/content/blog/use-terraform-to-deploy-an-azure-kubernetes-service-aks-cluster-traefik-2-cert-manager-and-lets-encrypt-certificates/index.md
+3
View File
@@ -10,4 +10,7 @@ curl -sfL https://get.k3s.io | sh -s - agent \
unset K3S_TOKEN
unset K3S_URL
# Disable firewall recommended
ufw disable
echo "K3s Node Join Completed"
+4 -1
View File
@@ -10,7 +10,7 @@ curl -sfL https://get.k3s.io | sh -s - server \
# --disable-cloud-controller \
# --disable servicelb \
# --disable local-storage \
# --disable traefik \
# --disable traefik
unset INSTALL_K3S_NAME
unset K3S_TOKEN
@@ -19,4 +19,7 @@ unset K3S_KUBECONFIG_MODE
echo "Installing Helm"
curl -sfL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
# Disable firewall recommended
ufw disable
echo "K3s Setup Completed"
+49 -8
View File
@@ -56,6 +56,30 @@ resource "binarylane_server" "server" {
wait_for_create = 60 # Must wait for the server to be ready before creating firewall rules
}
data "external" "kubeconfig" {
program = [
"/usr/bin/ssh",
"-o UserKnownHostsFile=/dev/null",
"-o StrictHostKeyChecking=no",
"root@${binarylane_server.server.0.permalink}",
"echo '{\"kubeconfig\":\"'$(cat /etc/rancher/k3s/k3s.yaml | base64)'\"}'",
]
}
resource "local_sensitive_file" "kubeconfig" {
depends_on = [binarylane_server.server]
content = replace(
base64decode(
replace(data.external.kubeconfig.result.kubeconfig, " ", "")
),
"server: https://127.0.0.1:6443",
"server: https://${binarylane_server.server.0.permalink}:6443",
)
filename = "${path.module}/.kube/config"
file_permission = "0600"
}
# k3s Agents
# ----------
data "cloudinit_config" "agent" {
@@ -110,14 +134,15 @@ resource "binarylane_server_firewall_rules" "example" {
server_id = each.value.id
firewall_rules = [
{
description = "K3s supervisor and Kubernetes API Server"
protocol = "tcp"
source_addresses = local.agent_ips
destination_addresses = local.server_ips
destination_ports = ["6443"]
action = "accept"
},
# TODO: Due to a bug, this only works if you uncomment AFTER creating servers
# {
# description = "K3s supervisor and Kubernetes API Server"
# protocol = "tcp"
# source_addresses = ["0.0.0.0/0"]
# destination_addresses = local.server_ips
# destination_ports = ["6443"]
# action = "accept"
# },
{
description = "Flannel VXLAN"
protocol = "udp"
@@ -134,5 +159,21 @@ resource "binarylane_server_firewall_rules" "example" {
destination_ports = ["10250"]
action = "accept"
},
{
description = "SSH"
protocol = "udp"
source_addresses = ["0.0.0.0/0"]
destination_addresses = [binarylane_vpc.example.ip_range]
destination_ports = ["22"]
action = "accept"
},
{
description = "HTTP"
protocol = "tcp"
source_addresses = ["0.0.0.0/0"]
destination_addresses = [binarylane_vpc.example.ip_range]
destination_ports = ["80"]
action = "accept"
},
]
}
+64
View File
@@ -0,0 +1,64 @@
resource "kubectl_manifest" "hello" {
yaml_body = <<YAML
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-hello
labels:
app: nginx-hello
spec:
selector:
matchLabels:
app: nginx-hello
template:
metadata:
labels:
app: nginx-hello
spec:
containers:
- image: nginxdemos/hello
name: nginx-hello
ports:
- name: http
containerPort: 8080
protocol: TCP
YAML
}
resource "kubectl_manifest" "hello_service" {
yaml_body = <<YAML
apiVersion: v1
kind: Service
metadata:
name: nginx-hello
labels:
app: nginx-hello
spec:
type: ClusterIP
selector:
app: nginx-hello
ports:
- port: 80
YAML
}
resource "kubectl_manifest" "hello_ingress" {
yaml_body = <<YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-hello
spec:
rules:
- host: "*.bnr.la"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-hello
port:
number: 80
YAML
}
+18
View File
@@ -3,7 +3,25 @@ terraform {
binarylane = {
source = "oscarhermoso/binarylane"
}
kubectl = {
source = "alekc/kubectl"
version = ">= 2.0.0"
}
}
}
provider "binarylane" {}
provider "helm" {
kubernetes {
host = "${binarylane_server.server.0.permalink}:6443"
config_path = local_sensitive_file.kubeconfig.filename
insecure = true # TODO
}
}
provider "kubectl" {
host = "${binarylane_server.server.0.permalink}:6443"
config_path = local_sensitive_file.kubeconfig.filename
insecure = true # TODO
}
+5 -7
View File
@@ -28,13 +28,11 @@ resource "binarylane_ssh_key" "example" {
}
resource "binarylane_server" "example" {
name = "tf-example-ssh"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
ssh_keys = [
binarylane_ssh_key.example.id
]
name = "tf-example-ssh"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
ssh_keys = [binarylane_ssh_key.example.id]
# If you are using a global default SSH key, you need to explicitly wait for
# it to be created before creating the server.
+4 -4
View File
@@ -9,13 +9,13 @@ terraform {
provider "binarylane" {}
resource "binarylane_vpc" "example" {
name = "tf-example"
name = "tf-example-vpc"
ip_range = "10.240.0.0/16"
}
# Web & NAT server
resource "binarylane_server" "web" {
name = "tf-vpc-example-web"
name = "tf-example-vpc-web"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
@@ -25,7 +25,7 @@ resource "binarylane_server" "web" {
# Database server
resource "binarylane_server" "db" {
name = "tf-vpc-example-db"
name = "tf-example-vpc-db"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
@@ -35,7 +35,7 @@ resource "binarylane_server" "db" {
# VPN server
resource "binarylane_server" "vpn" {
name = "tf-vpc-example-vpn"
name = "tf-example-vpc-vpn"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
+53 -24
View File
@@ -8,6 +8,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource"
d_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)
@@ -25,6 +26,13 @@ type serverDataSource struct {
bc *BinarylaneClient
}
type serverDataModel struct {
resources.ServerModel
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
Permalink types.String `tfsdk:"permalink"`
}
func (d *serverDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
@@ -63,20 +71,42 @@ func (d *serverDataSource) Schema(ctx context.Context, req datasource.SchemaRequ
MarkdownDescription: id.GetMarkdownDescription(),
Required: true, // ID is required to find the server
}
// Additional attributes
resp.Schema.Attributes["permalink"] = &schema.StringAttribute{
Description: "TODO",
MarkdownDescription: "TODO",
Optional: true,
}
publicIpv4AddressesDescription := "The public IPv4 addresses assigned to the server."
resp.Schema.Attributes["public_ipv4_addresses"] = &schema.ListAttribute{
Description: publicIpv4AddressesDescription,
MarkdownDescription: publicIpv4AddressesDescription,
ElementType: types.StringType,
Optional: true,
}
privateIpv4AddressesDescription := "The private IPv4 addresses assigned to the server."
resp.Schema.Attributes["private_ipv4_addresses"] = &schema.ListAttribute{
Description: privateIpv4AddressesDescription,
MarkdownDescription: privateIpv4AddressesDescription,
ElementType: types.StringType,
Optional: true,
}
}
func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data resources.ServerModel
var data serverDataModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
diags := req.Config.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// TODO: Use go routines here to make requests concurrently
// Read server
serverResp, err := d.bc.client.GetServersServerIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
@@ -94,32 +124,31 @@ func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
// TODO: Fetch user data
// Read user data
// userDataResp, err := d.bc.client.GetServersServerIdUserDataWithResponse(ctx, data.Id.ValueInt64())
// if err != nil {
// resp.Diagnostics.AddError(
// fmt.Sprintf("Error reading server user data: name=%s", data.Id.String()),
// err.Error(),
// )
// return
// }
// if userDataResp.StatusCode() != http.StatusOK {
// resp.Diagnostics.AddError(
// fmt.Sprintf("Unexpected HTTP status code reading server user data: name=%s", data.Id.String()),
// fmt.Sprintf("Unexpected HTTP status code reading server user data: %s", serverResp.Body),
// )
// return
// }
// Set data values
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
// data.UserData = types.StringValue(*userDataResp.JSON200.UserData)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
var publicIpv4Addresses []string
var privateIpv4Addresses []string
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
} else {
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
}
}
data.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
resp.Diagnostics.Append(diags...)
data.PrivateIPv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
resp.Diagnostics.Append(diags...)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+19 -10
View File
@@ -29,9 +29,9 @@ import (
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &serverResource{}
_ resource.ResourceWithConfigure = &serverResource{}
// _ resource.ResourceWithImportState = &serverResource{}
_ resource.Resource = &serverResource{}
_ resource.ResourceWithConfigure = &serverResource{}
_ resource.ResourceWithImportState = &serverResource{}
)
// Helper function to simplify the provider implementation.
@@ -45,10 +45,11 @@ type serverResource struct {
type serverModel struct {
resources.ServerModel
WaitForCreateSeconds types.Int32 `tfsdk:"wait_for_create"`
PublicIpv4Count types.Int32 `tfsdk:"public_ipv4_count"`
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
WaitForCreateSeconds types.Int32 `tfsdk:"wait_for_create"`
PublicIpv4Count types.Int32 `tfsdk:"public_ipv4_count"`
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
Permalink types.String `tfsdk:"permalink"`
}
func (d *serverResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
@@ -172,9 +173,6 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
Validators: []validator.Int32{
int32validator.AtLeast(0),
},
// PlanModifiers: []planmodifier.Int32{
// int32planmodifier.UseStateForUnknown(),
// },
}
publicIpv4AddressesDescription := "The public IPv4 addresses assigned to the server."
@@ -204,6 +202,15 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
listplanmodifier.UseStateForUnknown(),
},
}
resp.Schema.Attributes["permalink"] = &schema.StringAttribute{
Description: "TODO",
MarkdownDescription: "TODO",
// read only
Optional: false,
Required: false,
Computed: true,
}
}
func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
@@ -270,6 +277,7 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
var publicIpv4Addresses []string
var privateIpv4Addresses []string
@@ -375,6 +383,7 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
var publicIpv4Addresses []string
var privateIpv4Addresses []string
@@ -80,6 +80,7 @@ echo "Hello World" > /var/tmp/output.txt
resource.TestCheckResourceAttr("binarylane_server.test", "port_blocking", "true"),
resource.TestCheckResourceAttr("binarylane_server.test", "ssh_keys.#", "1"), // Only the test SSH key should be registered
resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.test", "id"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "permalink"),
// Verify data source values
resource.TestCheckResourceAttrPair("data.binarylane_server.test", "id", "binarylane_server.test", "id"),
@@ -87,6 +88,7 @@ echo "Hello World" > /var/tmp/output.txt
resource.TestCheckResourceAttr("data.binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-12"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"),
resource.TestCheckResourceAttrPair("data.binarylane_server.test", "permalink", "binarylane_server.test", "permalink"),
),
},
{
+8 -7
View File
@@ -7,6 +7,7 @@ import (
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
@@ -17,9 +18,9 @@ import (
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &sshKeyResource{}
_ resource.ResourceWithConfigure = &sshKeyResource{}
// _ resource.ResourceWithImportState = &serverResource{}
_ resource.Resource = &sshKeyResource{}
_ resource.ResourceWithConfigure = &sshKeyResource{}
_ resource.ResourceWithImportState = &serverResource{}
)
func NewSshKeyResource() resource.Resource {
@@ -217,7 +218,7 @@ func (r *sshKeyResource) Delete(ctx context.Context, req resource.DeleteRequest,
}
}
// func (r *sshKeyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// // Retrieve import ID and save to id attribute
// resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
// }
func (r *sshKeyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// Retrieve import ID and save to id attribute
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}