こんにちは。KOUKIです。とある企業でWeb開発業務に従事しています。
ここ最近は、k8s技術の習得を頑張っています。開発だけではなくDevOps分野でも活躍したいなと。
前回は、k8sのVolumeについて一通り学びました。
今回は、Networkingについて学んでいこうと思います。だた、今回の記事は開発環境の構築がメインです。
<目次>
k8s開発環境の実装
今回のdemoで使うアプリケーションを作成します。ちなみにGo言語で実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
mkdir auth-api tasks-api users-api touch auth-api/main.go auth-api/Dockerfile touch tasks-api/main.go tasks-api/Dockerfile touch users-api/main.go users-api/Dockerfile touch docker-compose.yml cd auth-api/ go mod init auth cd .. go mod init tasks cd .. go mod init users cd .. $ tree . ├── auth-api │ ├── Dockerfile │ ├── go.mod │ └── main.go ├── docker-compose.yml ├── tasks-api │ ├── Dockerfile │ ├── go.mod │ ├── main.go │ └── users-api ├── Dockerfile ├── go.mod └── main.go |
実装するアプリケーションは、全部で3つもあります。Networkingを確認するために、複数のPodが必要だからです。
Dockerfileの準備
Dockerfileには、それぞれ以下のコードを実装してください。
1 2 3 4 5 6 7 8 9 10 11 |
# auth-api/Dockerfile FROM golang:1.15-alpine WORKDIR /app COPY . . EXPOSE 80 CMD ["go", "run", "main.go"] |
1 2 3 4 5 6 7 8 9 10 |
# tasks-api FROM golang:1.15-alpine WORKDIR /app COPY . . EXPOSE 8000 CMD ["go", "run", "main.go"] |
1 2 3 4 5 6 7 8 9 10 |
# users-api FROM golang:1.15-alpine WORKDIR /app COPY . . EXPOSE 8080 CMD ["go", "run", "main.go"] |
Dockerfileは、EXPOSEの指定が違うだけで他は全て同じです。
docker-compose.ymlの準備
次に、docker-compose.ymlを用意しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
version: "3" services: auth: build: context: ./auth-api dockerfile: Dockerfile ports: - "80:80" container_name: auth users: build: context: ./users-api dockerfile: Dockerfile ports: - "8080:8080" container_name: users environment: AUTH_SERVICE_SERVICE_HOST: auth AUTH_ADDRESS: auth tasks: build: context: ./tasks-api dockerfile: Dockerfile ports: - "8000:8000" environment: TASKS_FOLDER: "./tasks/tasks.txt" AUTH_ADDRESS: auth container_name: tasks volumes: - ./tasks-api/tasks:/app/tasks |
auth-api APIの実装
auth-apiには、3つのエンドポイントを用意します。
Networkingの動きを確かめるためのAPIなので、実装には力を入れないでおきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
// auth-api/main.go package main import ( "net/http" "github.com/labstack/echo" ) // GET用の送信データ type sendMsg struct { Message string `json:"message"` UID string `json:"uid"` Token string `json:"token"` HashedPassword string `json:"hashed_password"` } func main() { // https://github.com/labstack/echo e := echo.New() // Routes e.GET("/verify-token/:token", tokenHandler) e.GET("/token/:hashedPassword/:enteredPassword", hashHandler) e.GET("/hashed-password/:password", passwordHandler) // Start Server e.Logger.Fatal(e.Start(":80")) } func tokenHandler(c echo.Context) error { token := c.Param("token") // ダミートークン if token == "abc" { return c.JSON(http.StatusOK, sendMsg{Message: "Valid token", UID: "u1"}) } return c.JSON(http.StatusUnauthorized, sendMsg{Message: "token invalid."}) } func hashHandler(c echo.Context) error { hashedPassword := c.Param("hashedPassword") enteredPassword := c.Param("enteredPassword") // dummy password verification! if hashedPassword == enteredPassword+"_hash" { const token = "abc" return c.JSON(http.StatusOK, sendMsg{Message: "Token created.", Token: token}) } return c.JSON(http.StatusUnauthorized, sendMsg{Message: "Passwords do not match."}) } func passwordHandler(c echo.Context) error { // dummy hashed pw generation! enteredPassword := c.Param("password") hashedPassword := enteredPassword + "_hash" return c.JSON(http.StatusOK, sendMsg{HashedPassword: hashedPassword}) } |
tasks-api APIの実装
tasks-apiには、2つのエンドポイントを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
// tasks-api/main.go package main import ( "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "os" "strings" "github.com/labstack/echo" ) var ( authAddr = os.Getenv("AUTH_ADDRESS") // docker-compose.ymlに設定した環境変数 authURL = fmt.Sprintf("http://%s/", authAddr) filePath = os.Getenv("TASKS_FOLDER") ) // GET用の送信データ type sendMsg struct { Message string `json:"message"` Tasks []task `json:"tasks"` } type uidRecvMsg struct { UID string `json:"uid"` } type task struct { Title string `json:"title"` Text string `json:"text"` } func extractAndVerifyToken(c echo.Context) (string, error) { authHeader := c.Request().Header.Get("Authorization") if authHeader == "" { return "", errors.New(fmt.Sprintf("%s", "No token provided.")) } // expects Bearer TOKEN token := strings.Split(authHeader, " ")[1] response, err := http.Get(authURL + "verify-token/" + token) if err != nil { return "", err } defer response.Body.Close() r := new(uidRecvMsg) if err = json.NewDecoder(response.Body).Decode(r); err != nil { return "", err } return r.UID, nil } func main() { // https://github.com/labstack/echo e := echo.New() // CORSの設定 e.Use(middleware.CORS()) // Routes e.GET("/tasks", getTaskHandler) e.POST("/tasks", postTaskHandler) // Start Server e.Logger.Fatal(e.Start(":8000")) } func getTaskHandler(c echo.Context) error { uid, err := extractAndVerifyToken(c) if err != nil { return c.JSON(http.StatusNotAcceptable, sendMsg{Message: "Could not verify token."}) } fmt.Println(uid) // このuidは特に使わない bytes, err := ioutil.ReadFile(filePath) if err != nil { return c.JSON(http.StatusInternalServerError, sendMsg{Message: "Loading the tasks failed."}) } strData := string(bytes) entries := strings.Split(strData, "TASK_SPLIT") fmt.Println(entries) tasks := []task{} for _, entry := range entries { fmt.Println(entry) if entry != "" { t := new(task) err := json.Unmarshal([]byte(entry), &t) if err != nil { return c.JSON(http.StatusInternalServerError, sendMsg{Message: "Faild JSON Parse."}) } tasks = append(tasks, *t) } } return c.JSON(http.StatusOK, sendMsg{Message: "Tasks loaded.", Tasks: tasks}) } func postTaskHandler(c echo.Context) error { uid, err := extractAndVerifyToken(c) if err != nil { return c.JSON(http.StatusNotAcceptable, sendMsg{Message: "Could not verify token."}) } fmt.Println(uid) // このuidは特に使わない t := new(task) if err := c.Bind(&t); err != nil { return c.JSON(http.StatusInternalServerError, sendMsg{Message: "Storing the task failed."}) } f, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return c.JSON(http.StatusInternalServerError, sendMsg{Message: "Storing the text failed."}) } defer f.Close() json, err := json.Marshal(t) if err != nil { return c.JSON(http.StatusInternalServerError, sendMsg{Message: "Faild JSON Parse."}) } f.WriteString(string(json) + "TASK_SPLIT") return c.JSON(http.StatusCreated, t) } |
users-api APIの実装
users-apiには、2つのエンドポイントを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
// users-api/main.go package main import ( "encoding/json" "fmt" "log" "net/http" "os" "strings" "github.com/labstack/echo" ) type sendMsg struct { Message string `json:"message"` } type recvMsg struct { Email string `json:"email"` Password string `json:"password"` } type hashedPasswordRecvMsg struct { HashedPassword string `json:"hashed_password"` } var ( authAddr = os.Getenv("AUTH_SERVICE_SERVICE_HOST") // docker-compose.ymlに設定した環境変数 authURL = fmt.Sprintf("http://%s/", authAddr) ) func main() { // https://github.com/labstack/echo e := echo.New() // Routes e.POST("/signup", signupHandler) e.POST("/login", loginHandler) // Start Server e.Logger.Fatal(e.Start(":8080")) } func signupHandler(c echo.Context) error { m := new(recvMsg) // It's just a dummy service - we don't really care for the email if err := c.Bind(&m); err != nil { return c.JSON(http.StatusInternalServerError, sendMsg{Message: "Storing the UserInfo failed."}) } if m.Password == "" || len(strings.Trim(m.Password, "")) == 0 || m.Email == "" || len(strings.Trim(m.Email, "")) == 0 { return c.JSON(http.StatusUnprocessableEntity, sendMsg{Message: "An email and password needs to be specified!"}) } response, err := http.Get(authURL + "hashed-password/" + m.Password) if err != nil { return c.JSON(http.StatusInternalServerError, sendMsg{Message: "Creating the user failed - please try again later."}) } defer response.Body.Close() r := new(hashedPasswordRecvMsg) if err = json.NewDecoder(response.Body).Decode(r); err != nil { return c.JSON(http.StatusInternalServerError, sendMsg{Message: "Creating the user failed - please try again later."}) } // since it's a dummy service, we don't really care for the hashed-pw either fmt.Println(r.HashedPassword, m.Email) return c.JSON(http.StatusCreated, sendMsg{Message: "User created!"}) } func loginHandler(c echo.Context) error { m := new(recvMsg) // It's just a dummy service - we don't really care for the email if err := c.Bind(m); err != nil { log.Fatal(err) } if m.Password == "" || len(strings.Trim(m.Password, "")) == 0 || m.Email == "" || len(strings.Trim(m.Email, "")) == 0 { return c.JSON(http.StatusUnprocessableEntity, sendMsg{Message: "An email and password needs to be specified!"}) } // normally, we'd find a user by email and grab his/ her ID and hashed password hashedPassword := m.Password + "_hash" response, err := http.Get(authURL + "token/" + hashedPassword + "/" + m.Password) if err != nil { return c.JSON(http.StatusInternalServerError, sendMsg{Message: "Internal Server Error"}) } defer response.Body.Close() if response.StatusCode == 200 { return c.JSON(http.StatusOK, sendMsg{Message: response.Status}) } return c.JSON(http.StatusUnauthorized, sendMsg{Message: "Logging in failed!"}) } |
コンテナのビルド/up
下記のコマンドでコンテナを作成しましょう。
1 2 |
docker-compose build docker-comopse up |
APIエンドポイント一覧
APIのエンドポイントの一覧です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// auth-api # GETリクエスト http://localhost/verify-token/abc http://localhost/hashed-password/abc http://localhost/token/abc_hash/abc // tasks-api # POSTリクエスト(AuthorizationとBodyが必要※詳細は下記に記載) http://localhost:8000/tasks # Getリクエスト(Authorizationが必要※詳細は下記に記載) http://localhost:8000/tasks // users-api # POSTリクエスト(Bodyが必要※詳細は下記に記載) http://localhost:8080/signup http://localhost:8080/login |
上記のエンドポイントは、Chromeの「Talend API Tester」で動作確認しています。
tasks-apiとusers-apiには、HeaderとBodyの設定が必要な箇所があるので、サンプル画像を載せておきます。
tasks-api POSTのパラメータ
「Barer」が必要なので注意しましょう。

ちなみに、このリクエストが成功するとtasks-apiフォルダ配下にtasksフォルダとtasks.txtが作成されます。
tasks-api GETのパラメータ

users-apiのPOSTパラメータ
passwordには、「abc」(固定値)を設定しましょう。


長くなったので、続きはまた次回にしましょう。
次回
次回からいよいよk8sのネットワーキングについて学んでいきます。
コメントを残す
コメントを投稿するにはログインしてください。