Gin Framework: Distinguish profiles based on cloud native environments

Posted by celsoendo on Wed, 24 Nov 2021 22:34:40 +0100

introduce

Using a complete example, in the Gin framework, profiles are distinguished based on the environment. That is, how to read different profiles in environments such as Testing and Online.

We will use rk-boot To start the Gin Framework microservice.

Visit the following address for the complete tutorial:

install

go get github.com/rookie-ninja/rk-boot

Quick Start

We will create three config files, config/beijing.yaml, config/shanghai.yaml, config/default.yaml, and then read different files based on different environment variables.

rk-boot uses REALM, REGION, AZ, DOMAIN environment variables to distinguish between different environments. This is also the recommended method for cloud native environment resolution.

For example, REALM="Your Business", "REGION="Beijing","AZ="Beijing Area", "DOMAIN="Test Environment".

rk-boot integration viper To process the configuration file.

1. Create a configuration file

  • config/beijing.yaml
---
region: beijing
  • config/shanghai.yaml
---
region: shanghai
  • config/default.yaml
---
region: default

2. Create boot.yaml

The boot.yaml file tells rk-boot how to start the Gin service.

Using config as the entry to the configuration file in boot.yaml, we can provide multiple config file paths.

Locale represents the Config environment, and we use locale to distinguish between different Configs.

Why does config.name use the same name?

We want to use the same set of code, but read different files, and we want different names for the files. So use locale to distinguish between different files. The logic of locale is detailed later.

config:
  # default
  - name: my-config
    locale: "*::*::*::*"
    path: config/default.yaml
  # Read this file if the environment variable REGION=beijing
  - name: my-config
    locale: "*::beijing::*::*"
    path: config/beijing.yaml
  # If the environment variable REGION=shanghai, read this file
  - name: my-config
    locale: "*::shanghai::*::*"
    path: config/shanghai.yaml
gin:
  - name: greeter
    port: 8080
    enabled: true

3. Create main.go

Set the environment variable: REGION="beijing", then read the configuration file, config/beijing.yaml will be read.

package main

import (
	"context"
	"fmt"
	"github.com/rookie-ninja/rk-boot"
	"os"
)

// Application entrance.
func main() {
	// Set REGION=beijing
	os.Setenv("REGION", "beijing")

	// Create a new boot instance.
	boot := rkboot.NewBoot()

	// Load config which is config/beijing.yaml
	fmt.Println(boot.GetConfigEntry("my-config").GetViper().GetString("region"))

	// Bootstrap
	boot.Bootstrap(context.Background())

	// Wait for shutdown sig
	boot.WaitForShutdownSig(context.Background())
}

4. Folder structure

$ tree
.
├── boot.yaml
├── config
│   ├── beijing.yaml
│   ├── default.yaml
│   └── shanghai.yaml
├── go.mod
├── go.sum
└── main.go

5. Validation

$ go run main.go

We will get the following output:

beijing

6. No matching environment variable found

If REGION="not-matched", that is, no matching environment variable is found, the default configuration file (config/default.yaml) is read. Because the locale property of config/default.yaml is *:*:*:*:*

// Application entrance.
func main() {
    // Set REGION=not-matched
	os.Setenv("REGION", "not-matched")
    
    ...
    // Load config which is config/beijing.yaml
	fmt.Println(boot.GetConfigEntry("my-config").GetViper().GetString("region"))
    ...
}
default

7. Environment variables are not configured

If we do not configure the REGION environment variable, the config/default.yaml file is read.

// Application entrance.
func main() {
    ...
    // Load config which is config/beijing.yaml
	fmt.Println(boot.GetConfigEntry("my-config").GetViper().GetString("region"))
    ...
}
default

concept

rk-boot uses four environment variables, REALM, REGION, AZ, DOMAIN, to distinguish profiles.

These four environment variables can be any value.

Best Practices

For example, we have a Cloud Album business. This can be configured if the business uses MySQL with different IP addresses in different environments.

Framework

Assume that our business has servers in both Beijing and Shanghai, and there are two zones in Beijing and Shanghai to improve service availability.

At this point, we can configure the following environment variables on the machine, which can be set in batches using tools such as Ansible.

Environmental Science

Corresponding environment variables

Beijing, District One, Test

REALM="cloud-album",REGION="bj",AZ="bj-1",DOMAIN="test"

Beijing, District 1, Online

REALM="cloud-album",REGION="bj",AZ="bj-1",DOMAIN="prod"

Beijing, Zone 2, Test

REALM="cloud-album",REGION="bj",AZ="bj-2",DOMAIN="test"

Beijing, Zone 2, Online

REALM="cloud-album",REGION="bj",AZ="bj-2",DOMAIN="prod"

Shanghai, District 1, Test

REALM="cloud-album",REGION="sh",AZ="sh-1",DOMAIN="test"

Shanghai, District 1, Online

REALM="cloud-album",REGION="sh",AZ="sh-1",DOMAIN="prod"

Shanghai, Zone 2, Test

REALM="cloud-album",REGION="sh",AZ="sh-2",DOMAIN="test"

Shanghai, Zone 2, Online

REALM="cloud-album",REGION="sh",AZ="sh-2",DOMAIN="prod"

At the same time, if we do not use services like ETCD, Consul to remotely pull configuration files, we can add the following files directly to the machine. Each file has a different MySQL IP address.

Folder structure

.
├── boot.yaml
├── config
│   ├── bj-1-test.yaml
│   ├── bj-1-prod.yaml
│   ├── bj-2-test.yaml
│   ├── bj-2-prod.yaml
│   ├── sh-1-test.yaml
│   ├── sh-1-prod.yaml
│   ├── sh-2-test.yaml
│   ├── sh-2-prod.yaml
│   └── default.yaml
├── go.mod
├── go.sum
└── main.go

boot.yaml

Next, we add the following config entry in boot.yaml.

config:
  # Default Entry
  - name: my-config
    locale: "*::*::*::*"
    path: config/default.yaml
  # BEIJING, DISTRICT 1, TEST ENVIRONMENT
  - name: my-config
    locale: "cloud-album::bj::bj-1::test"
    path: config/bj-1-test.yaml
  # Beijing, District 1, Online Environment
  - name: my-config
    locale: "cloud-album::bj::bj-1::prod"
    path: config/bj-1-prod.yaml
  # Beijing, Zone 2, Test Environment
  - name: my-config
    locale: "cloud-album::bj::bj-2::test"
    path: config/bj-2-test.yaml
  # Beijing, Zone 2, Online Environment
  - name: my-config
    locale: "cloud-album::bj::bj-2::prod"
    path: config/bj-2-prod.yaml
  # Shanghai, Area 1, Test Environment
  - name: my-config
    locale: "cloud-album::sh::sh-1::test"
    path: config/sh-1-test.yaml
  # Shanghai, Area 1, Online Environment
  - name: my-config
    locale: "cloud-album::sh::sh-1::prod"
    path: config/sh-1-prod.yaml
  # Shanghai, Zone 2, Test Environment
  - name: my-config
    locale: "cloud-album::sh::sh-2::test"
    path: config/sh-2-test.yaml
  # Shanghai, Zone 2, Online Environment
  - name: my-config
    locale: "cloud-album::sh::sh-2::prod"
    path: config/sh-2-prod.yaml
gin:
  - name: greeter
    port: 8080
    enabled: true

main.go reads the configuration file.

Because all Configs are named my-config, we can use my-config to get ConfigEntry while reading from main.go.

package main

import (
	"context"
	"fmt"
	"github.com/rookie-ninja/rk-boot"
	"os"
)

// Application entrance.
func main() {
	// Create a new boot instance.
	boot := rkboot.NewBoot()

	// Get viper instance based on environment variable
	boot.GetConfigEntry("my-config").GetViper()

	// Bootstrap
	boot.Bootstrap(context.Background())

	// Wait for shutdown sig
	boot.WaitForShutdownSig(context.Background())
}

Topics: Go Microservices Cloud Native gin