Skip to content

kylinsoong/golang

Repository files navigation

Golang Boot Camp

Getting started

Check go version

# go version
go version go1.21.5 linux/arm64

package, import, main

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Run main methods

$ go run hello.go
Hello, World!

Call External Methods

package main

import "fmt"

import "rsc.io/quote"

func main() {
    fmt.Println("Hello, World!")
    fmt.Println(quote.Glass())
    fmt.Println(quote.Go())
    fmt.Println(quote.Hello())
    fmt.Println(quote.Opt())
}

Getting started from real project

Work with Go standard project

Project Structure

# tree greetings/
greetings/
├── cmd
│   └── greetings
│       └── main.go
└── pkg
    └── greetings
        ├── greetings.go
        └── greetings_test.go

A standard Go project structure can vary depending on the size and nature of the project, but there are some common conventions that many Go developers follow:

  • cmd: The cmd directory is for the main applications of your project. Each application can have its own subdirectory. For example, myapp could be the main entry point for your application

  • internal: The internal directory is for packages that are only used within your project, not meant for external use.

  • pkg: The pkg directory contains libraries and packages that are meant to be used by other projects. Each package within pkg can have its own subdirectory.

Init and Setup Dependencies

go mod init github.com/kylinsoong/golang/greetings
go mod tidy

Run

go run cmd/greetings/main.go

How main module call pkg module

package main

import (
    "github.com/kylinsoong/golang/greetings/pkg/greetings"
)

func main() {
    names := []string{"Gladys", "Samantha", "Darrin", "Kylin"}
    messages, err := greetings.Hellos(names)
}

Run Unit Test

go test ./pkg/greetings/

Build

go build -o a.out cmd/greetings/*.go

Run Binary File

# ./a.out

Data Struct

simple map

    processedResources := make(map[string]bool)

    processedResources["foo.yaml"] = true
    processedResources["bar.yaml"] = false
    processedResources["zoo.yaml"] = false

    for key, value := range processedResources {
        fmt.Printf("%s: %v\n", key, value)
    }

    fmt.Println(processedResources["zoo.yaml"])

    value, exists := processedResources["coo.yaml"]
    if exists {
        fmt.Printf("coo.yaml: %v\n", value)
    } else {
        fmt.Println("coo.yaml not exist")
    }

simple struct

type WatchedNamespaces struct {
    Namespaces     []string
    NamespaceLabel string
}

func main() {
    watchedNamespaces := WatchedNamespaces{
        Namespaces:     []string{"namespace1", "namespace2"},
        NamespaceLabel: "watched",
    }

    fmt.Println(watchedNamespaces.Namespaces)
    fmt.Println(watchedNamespaces.NamespaceLabel)
}

struct with func field

Using a Go struct with a function field offers flexibility and allows you to encapsulate behavior within the struct while enabling dynamic customization.

type Manager struct {
    queueLen            int
    processAgentLabels  func(map[string]string, string, string) bool
}

func customProcessAgentLabels(labels map[string]string, namespace string, name string) bool {
    fmt.Printf("Custom Processing Agent Labels: %v, Namespace: %s, Name: %s\n", labels, namespace, name)
    return true
}

func main() {
    appMgr := Manager{
        queueLen:           10,
        processAgentLabels: customProcessAgentLabels,
    }
    appMgr.processAgentLabels(map[string]string{"key": "value"}, "exampleNamespace", "exampleName")
}

Fundamentals

object-oriented programming: interface composition

Go does not support traditional interface inheritance like some other object-oriented programming languages. Instead, Go uses a concept called "interface composition" or "embedding" to achieve similar goals without relying on classical inheritance.

In Go, you can embed one interface within another to create a new interface that includes the methods of the embedded interface.

Interface
type Interface interface {
	Add(item interface{})
	Len() int
	Get() (item interface{}, shutdown bool)
	Done(item interface{})
	ShutDown()
	ShutDownWithDrain()
	ShuttingDown() bool
}
DelayingInterface
type DelayingInterface interface {
	Interface
	AddAfter(item interface{}, duration time.Duration)
}
RateLimitingInterface
type RateLimitingInterface interface {
	DelayingInterface
	AddRateLimited(item interface{})
	Forget(item interface{})
	NumRequeues(item interface{}) int
}

Multi-threads

goroutine

The goroutine is a lightweight thread of execution managed by the Go runtime. Goroutines enable concurrent programming in a way that is more efficient and scalable compared to traditional threads.

package main

import (
        "fmt"
        "time"
)

func printNumbers() {
    for i := 1; i <= 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Printf("%d \n", i)
    }
}

func main() {
    go printNumbers()

    for i := 1; i <= 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Printf("A%d \n", i)
    }
}

channel: send and receive data

Channels are a typed conduit through which you can send and receive values with the channel operator :

  • ch ← v send v to channel

  • v := ←ch receive from channel, and assign value to v

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}

func Test_Send_Receive(t *testing.T) {
	s := []int{7, 2, 8, -9, 4, 0}
	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c
	fmt.Println(x, y, x+y)
}

channel: communication and synchronization between goroutines

In Go, channels are a powerful mechanism for communication and synchronization between goroutines. They provide a way for one goroutine to send data to another goroutine.

func numberGenerator(ch chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 1; i <= 5; i++ {
        ch <- i // Send numbers 1 to 5 to the channel
    }
    close(ch) // Close the channel to signal no more data will be sent
}

func squareCalculator(ch chan int, resultCh chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for num := range ch {
        square := num * num
        resultCh <- square // Send squared result to the resultCh channel
    }
    close(resultCh) // Close the resultCh channel to signal no more results will be sent
}

func resultPrinter(resultCh chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for result := range resultCh {
        fmt.Println("Squared Result:", result)
    }
}

func main() {
    numberCh := make(chan int)
    resultCh := make(chan int)
    var wg sync.WaitGroup
    wg.Add(3)
    go numberGenerator(numberCh, &wg)
    go squareCalculator(numberCh, resultCh, &wg)
    go resultPrinter(resultCh, &wg)
    wg.Wait()
}

channel: multiple chanels with select case statement

The select statement in Go is used to choose from multiple communication operations. It allows a goroutine to wait on multiple communication operations, blocking until one of them can proceed.

func simple_worker(c chan string) {
	c <- fmt.Sprintf("Hello from Channel %v", c)
}

func Test_Multiple_Chan_With_Select(t *testing.T) {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go simple_worker(ch1)
	go simple_worker(ch2)
	select {
	case msg1 := <-ch1:
		fmt.Println(msg1)
	case msg2 := <-ch2:
		fmt.Println(msg2)
	case <-time.After(3 * time.Second):
		fmt.Println("Timed out waiting for messages.")
	}
}

channel: signal completion of sub goroutine

func worker(ch chan struct{}) {
    fmt.Println("Worker is starting...")
    time.Sleep(2 * time.Second)
    fmt.Println("Worker is done!")
    ch <- struct{}{}
}

func main() {
    doneCh := make(chan struct{})
    go worker(doneCh)
    <-doneCh
    fmt.Println("Main function exiting.")
}

using signal to control application interruptiong and termination

In Go, the os/signal package provides a way to intercept signals sent to the program, such as termination signals (SIGINT for interrupt and SIGTERM for terminate). The signal usually wrapped with a channel that can be used to control application interruptiong and termination.

func main() {
    fmt.Println("Started to run tasks...")
    signals := make(chan os.Signal, 1)
    signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
    sig := <-signals
    fmt.Printf("Received signal: %v\n", sig)
}

defer statements a Last In, First Out (LIFO) execution order

func main() {
    defer fmt.Println("This will be executed third.")
    defer fmt.Println("This will be executed second.")
    defer fmt.Println("This will be executed first.")
    fmt.Println("Hello, Go!")
}

thread-safe via defer and sync

type Counter struct {
    value int
    mu    sync.Mutex
}

func (c *Counter) increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *Counter) getValue() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

K8S

How to repeatedly calls a provided function

The k8s.io/apimachinery/pkg/util/wait provides utilities for waiting and timing operations. Specifically, wait.Until is a function that repeatedly calls a provided function until the stop channel is closed or a timeout is reached.

wait.Until
func exampleWork() {
    fmt.Println("Doing some work...")
    time.Sleep(2 * time.Second)
}

func main() {
    stopCh := make(chan struct{})
    go wait.Until(exampleWork, time.Second, stopCh)
    time.Sleep(5 * time.Second)
    close(stopCh)
    time.Sleep(1 * time.Second)
    fmt.Println("Main goroutine exiting...")
}

How to use kubernetes rate limited workqueue

Refer to object-oriented programming: interface composition for more details about k8s.io/client-go/util/workqueue and RateLimitingInterface implemenration.

How to create kubernetes client

There are 2 stps necessary to create a kubeClient:

  • Create Kubernetes Rest Config, If your application run in Kubernetes, the use the certifications keys in Namespace default ServiceAccount, if your application run outside Kubernetes, then you need pass ~/.kube/config file to create Rest Config

  • Create Kubernetes Client via Kubernetes Rest Config

import "k8s.io/client-go/kubernetes"
import "k8s.io/client-go/rest"
import "k8s.io/client-go/tools/clientcmd"

var kubeClient  kubernetes.Interface
var config      *rest.Config
var err         error


if *inCluster {
    config, err = rest.InClusterConfig()
} else {
    config, err = clientcmd.BuildConfigFromFlags("", *kubeConfig)
}
if err != nil {
    log.Fatalf("[INIT] error creating configuration: %v", err)
}

kubeClient, err = kubernetes.NewForConfig(config)
if err != nil {
    log.Fatalf("[INIT] error connecting to the client: %v", err)
}

How to use kubernetes client-side caching mechanism and cache api create a configmap watcher

k8s.io/client-go/tools/cache is a client-side caching mechanism. It is useful for reducing the number of server calls you’d otherwise need to make. Reflector watches a server and updates a Store. Two stores are provided; one that simply caches objects (for example, to allow a scheduler to list currently available nodes), and one that additionally acts as a FIFO queue (for example, to allow a scheduler to process incoming pods).

	cfgMapInformer = cache.NewSharedIndexInformer(
		cache.NewFilteredListWatchFromClient(
			restClientv1,
			Configmaps,
			namespace,
			everything,
		),
		&v1.ConfigMap{},
		resyncPeriod,
		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
	)

	cfgMapInformer.AddEventHandlerWithResyncPeriod(
		&cache.ResourceEventHandlerFuncs{
			AddFunc:    func(obj interface{}) { enqueueConfigMap(obj, OprTypeCreate) },
			UpdateFunc: func(old, cur interface{}) { enqueueConfigMap(cur, OprTypeUpdate) },
			DeleteFunc: func(obj interface{}) { enqueueConfigMap(obj, OprTypeDelete) },
		},
		resyncPeriod,
	)

About

golang self learning & boot camp

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published