docker logs
? This is the 12FA in action! The Docker and 12 Factor App is a killer combination that provides a quick overview of the design and deployment of future applications.
Many applications that are packaged in Docker have some minor flaws. This is more like microcracks - applications continue to work, but can cause hellish torment when working with them.
docker run
.As soon as you transfer everything to Docker and refuse to use tools that do not have the Docker logo, you will put yourself in an impossible position.
package main import ( "database/sql" "encoding/json" "fmt" "io/ioutil" "log" "net" "os" _ "github.com/go-sql-driver/mysql" ) var ( config Config db *sql.DB ) type Config struct { DataDir string `json:"datadir"` // Database settings. Host string `json:"host"` Port string `json:"port"` Username string `json:"username"` Password string `json:"password"` Database string `json:"database"` } func main() { log.Println("Starting application...") // Load configuration settings. data, err := ioutil.ReadFile("/etc/config.json") if err != nil { log.Fatal(err) } if err := json.Unmarshal(data, &config); err != nil { log.Fatal(err) } // Use working directory. _, err = os.Stat(config.DataDir) if err != nil { log.Fatal(err) } // Connect to database. hostPort := net.JoinHostPort(config.Host, config.Port) dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=30s", config.Username, config.Password, hostPort, config.Database) db, err = sql.Open("mysql", dsn) if err != nil { log.Fatal(err) } if err := db.Ping(); err != nil { log.Fatal(err) } }
docker build
: $ GOOS=linux go build -o app
docker run
: FROM scratch MAINTAINER Kelsey Hightower <kelsey.hightower@gmail.com> COPY app /app ENTRYPOINT ["/app"]
docker build
: $ docker build -t app:v1 .
docker run
: $ docker run --rm app:v1 2015/12/13 04:00:34 Starting application... 2015/12/13 04:00:34 open /etc/config.json: no such file or directory
/etc/config.json
configuration file. I can fix this by mounting the configuration file at runtime: $ docker run --rm \ -v /etc/config.json:/etc/config.json \ app:v1 2015/12/13 07:36:27 Starting application... 2015/12/13 07:36:27 stat /var/lib/data: no such file or directory
/var/lib/data
directory does not exist. I can easily bypass the missing directory by mounting another host directory in the container: $ docker run --rm \ -v /etc/config.json:/etc/config.json \ -v /var/lib/data:/var/lib/data \ app:v1 2015/12/13 07:44:18 Starting application... 2015/12/13 07:44:48 dial tcp 203.0.113.10:3306: i/o timeout
I hear silent screams from hipsters “sys-admins”, impatiently waiting to suggest using the Docker user entry point to solve our bootstrap problems.
/etc/config.json
configuration file and creating the missing /var/lib/data
directory during the startup process. The script executes the sample application as the final stage, retaining the original behavior when the application is started by default. #!/bin/sh set -e datadir=${APP_DATADIR:="/var/lib/data"} host=${APP_HOST:="127.0.0.1"} port=${APP_PORT:="3306"} username=${APP_USERNAME:=""} password=${APP_PASSWORD:=""} database=${APP_DATABASE:=""} cat <<EOF > /etc/config.json { "datadir": "${datadir}", "host": "${host}", "port": "${port}", "username": "${username}", "password": "${password}", "database": "${database}" } EOF mkdir -p ${APP_DATADIR} exec "/app"
FROM alpine:3.1 MAINTAINER Kelsey Hightower <kelsey.hightower@gmail.com> COPY app /app COPY docker-entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]
Notice that the custom shell script is copied to the Docker image and used as an entry point instead of the application's binary file.
$ docker build -t app:v2 .
$ docker run --rm \ -e "APP_DATADIR=/var/lib/data" \ -e "APP_HOST=203.0.113.10" \ -e "APP_PORT=3306" \ -e "APP_USERNAME=user" \ -e "APP_PASSWORD=password" \ -e "APP_DATABASE=test" \ app:v2 2015/12/13 04:44:29 Starting application...
FROM alpine:3.1
$ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE app v2 1b47f1fbc7dd 2 hours ago 10.99 MB app v1 42273e8664d5 2 hours ago 5.952 MB
// Load configuration settings. data, err := ioutil.ReadFile("/etc/config.json") // Fallback to default values. switch { case os.IsNotExist(err): log.Println("Config file missing using defaults") config = Config{ DataDir: "/var/lib/data", Host: "127.0.0.1", Port: "3306", Database: "test", } case err == nil: if err := json.Unmarshal(data, &config); err != nil { log.Fatal(err) } default: log.Println(err) }
log.Println("Overriding configuration from env vars.") if os.Getenv("APP_DATADIR") != "" { config.DataDir = os.Getenv("APP_DATADIR") } if os.Getenv("APP_HOST") != "" { config.Host = os.Getenv("APP_HOST") } if os.Getenv("APP_PORT") != "" { config.Port = os.Getenv("APP_PORT") } if os.Getenv("APP_USERNAME") != "" { config.Username = os.Getenv("APP_USERNAME") } if os.Getenv("APP_PASSWORD") != "" { config.Password = os.Getenv("APP_PASSWORD") } if os.Getenv("APP_DATABASE") != "" { config.Database = os.Getenv("APP_DATABASE") }
// Use working directory. _, err = os.Stat(config.DataDir) if os.IsNotExist(err) { log.Println("Creating missing data directory", config.DataDir) err = os.MkdirAll(config.DataDir, 0755) } if err != nil { log.Fatal(err) }
$ docker run --rm \ -e "APP_DATADIR=/var/lib/data" \ -e "APP_HOST=203.0.113.10" \ -e "APP_PORT=3306" \ -e "APP_USERNAME=user" \ -e "APP_PASSWORD=password" \ -e "APP_DATABASE=test" \ app:v3 2015/12/13 05:36:10 Starting application... 2015/12/13 05:36:10 Config file missing using defaults 2015/12/13 05:36:10 Overriding configuration from env vars. 2015/12/13 05:36:10 Creating missing data directory /var/lib/data 2015/12/13 05:36:10 Connecting to database at 203.0.113.10:3306 2015/12/13 05:36:40 dial tcp 203.0.113.10:3306: i/o timeout 2015/12/13 05:37:11 dial tcp 203.0.113.10:3306: i/o timeout
Notice in the above output I am not able to connect to a working target database located at 203.0.113.10 ..
$ gcloud sql instances patch mysql \ --authorized-networks "203.0.113.20/32"
2015/12/13 05:37:43 dial tcp 203.0.113.10:3306: i/o timeout 2015/12/13 05:37:46 Application started successfully.
// Connect to database. hostPort := net.JoinHostPort(config.Host, config.Port) log.Println("Connecting to database at", hostPort) dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=30s", config.Username, config.Password, hostPort, config.Database) db, err = sql.Open("mysql", dsn) if err != nil { log.Println(err) } var dbError error maxAttempts := 20 for attempts := 1; attempts <= maxAttempts; attempts++ { dbError = db.Ping() if dbError == nil { break } log.Println(dbError) time.Sleep(time.Duration(attempts) * time.Second) } if dbError != nil { log.Fatal(dbError) }
log.Println("Application started successfully.")
Source: https://habr.com/ru/post/273983/
All Articles