Go言語で作るJson Web Tokenを用いたセキュアなAPIをインフラ構築自動化技術を用いて(Docker, Terraform, Ansible)構築

More than 1 year has passed since last update.


初めに

著者はGo言語、インフラ構築自動化技術に関する知識はあまりないので、知識のある上級者は適切なアドバイスなり、間違いの指摘をして頂けると幸です。

このようなシチュエーションを想定しています。

ある日、上司からこんな無茶ぶりが・・・


「君、優秀なんだね。Go言語が良いみたいだからGo言語でセキュアなAPIを軽く作っておいてよ。ついでにインフラ構築自動化も流行ってるから、導入して実行環境の構築も自動化しといて」


という無茶ぶりされたときにあなたはこのようなまなざしを上司に向けるでしょう。

DI_IMG_5648_TP_V.jpg

安心して下さい。そんなあなたのためにこの記事を書きました。笑

冗談はさておき、本題に入ります。


構成

今回の構成を図に表してみます。

Screen Shot 2016-02-29 at 22.03.18.jpg

図の参照元

Go Lang, GitLab, Vagrant, Docker, CircleCi, Ansible, Terraform, AWS

工夫した点


  1. 開発環境と実行テスト環境の明示的な切り分け(commitしないと動作確認できない仕組み)

  2. DockerのマルチコンテナでDBコンテナとwebサーバーコンテナを用意して、AWS上で実現しようとする環境の模倣

  3. terraformでスケールしても対応可能なauto scale 構成のEC2とDatabaseにはRDSを構築

  4. AnsibleでAWSのEC2の環境を自動構築

本来はEC2の環境もDockerで構成した方がベターな気もしますが、EC2上に複数のサービスを立てる必要がなかったので今回のようにしました。


各主要技術の採用理由

上記の環境は他の言語、また手動でも実現可能です。では学習コストを払ってでも各技術における採用すべき正当な理由を上げてみます。


GO言語


  1. コンパイル時にすべてのパッケージは静的にリンクされるため、実行ファイルは1つのバイナリファイルになるため、依存関係に悩まされず、ファイルも軽いのでデプロイの心配が少なく運用が楽

  2. インストールは比較的簡単

  3. チュートリアルも充実しており、その場で試せるプレイグラウンドもWeb上に用意されており、気軽に学習を始める事が可能

参考

(採用事例で学ぶGoLangの使いドコロ )


Json Web Token


  1. 認証用のトークンの期限を決められる。またRSA方式を利用すれば秘密鍵と公開鍵を用いた認証が可能

  2. HTTPヘッダーに載せられるため、非常に軽く、必要な情報を全て載せることが可能

  3. 認証のためにData Baseを必要としない。

参考

(Introduction to JSON Web Tokens)


Docker

メリット、デメリットの所感を記述した記事があったのこちらをご参照下さい。

開発におけるDocker導入のメリット


Terraform


  1. AWS, Google Cloud PlatForm, Herokuなどの主要なwebサービスの構築の自動化

  2. 構築した環境をgitで管理することで変更履歴を残せる

  3. 明示的に環境の再現が可能

  4. 複製が容易

  5. コードとシステムが一致(設計書のように実際の環境とのズレが生じない)

  6. 他のサービス(Jenkinsなど)と連携することで自動テストも可能

Step by stepで学ぶTerraformによる監視付きAWS構築


Ansible


  1. ymlでかけるため簡単

  2. エージェントレス(構成される側の準備は不要!!)

  3. 冪等性(ある操作を何回行っても同じ結果)

インフラ自動構築エンジン "Ansible"の勘所を1日でつかむ ~基礎入門編~


Go言語

全体のシステムの中で注目して欲しい部分を赤線で囲っています。

Screen Shot 2016-03-01 at 5.52.32.jpg

APIサーバーはGo言語で記述しました。

記述の際は有名なrevelというフレームワークを使用しました。

DI_IMG_5648_TP_V.jpg

revelを採用した理由は下記です。


  1. testのための機能が揃っている

  2. deployも簡単

  3. 必要なサンプルがある

私の中では必要なサンプルがあるが重要で自分で一から全て書くのはハマりどころが多くモチベーションが下がります。

ですがサンプルがあることによって、コードリーディングを自然にするようになり、王道の記述の仕方の仕方が分るようになるので良いのではないでしょうか。

これは人によるので、あくまで私の意見です。

今回の参考にしたサンプルは下記です。

Booking


DataBase

Screen Shot 2016-03-01 at 5.52.32.jpg

参照元

http://linuxserver.jp/wp-content/uploads/2015/06/mysql_hosting.png

こちらを参考にした理由はDataBaseを使用したコードが載っていたためです。

このサンプルではgorpを使用してORMラッパーを実現して実装されています。

変更した部分はDBアクセス部分です。

変更前

    "github.com/go-gorp/gorp"

_ "github.com/mattn/go-sqlite3"
:         
:
db.Init()
Dbm = &gorp.DbMap{Db: db.Db, Dialect: gorp.SqliteDialect{}}

変更後

    "github.com/go-gorp/gorp"

_ "github.com/go-sql-driver/mysql"
:         
:
uri := read_conf("db.uri")
db_access_uri := "ユーザー名:パスワード@tcp(" + uri +")/データベース名"
db, err := sql.Open("mysql", db_access_uri)
if err != nil {
panic(err)
}
Dbm = &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}

主な変更点

1. mysqlのdriverをインストールすることによってmysqlを扱えるようにしました。

2. DataBaseサーバーのアドレスをapp.confから取得できるようにしました。

3. mysql用のアクセスのコードに変えています。

使用したいSQLを変更したい場合は下記のSQLドライバーから選択し、下記の部分をそのドライバー用に書き換える必要があります。

    _ "github.com/go-sql-driver/mysql"

Dbm = &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}

SQLドライバー一覧

csvファイルを読み込んで、Databaseに反映させたい場合は下記のようなコードになります。


  1. csvファイルの読み込みます

  2. 読み込んだファイルをモデルファイルに反映します

  3. スライスに保存します

  4. スライスからデータを取得してData Baseに書き込み処理します

    prev, err := filepath.Abs(".")

if err != nil {
defer fmt.Println("Error")
}

file, err := os.Open(prev + "/csv_diary_data/登録したいcsvファイル")
failOnError(err)
defer file.Close()

f_count := 0
reader := csv.NewReader(file)
var model []*models.定義したモデル名

for {
recoad, err := reader.Read()
if err == io.EOF {
break
} else {
failOnError(err)
}
//recordの数字はcsvファイルの列数に一致
tmp_list = append(tmp_list, &models.定義したモデル名{recoad[0], record[1],
recoad[2], recoad[3]})
f_count++
}
for _, model := range tmp_list {
if err := Dbm.Insert(model); err != nil {
panic(err)
}
}


Json Web Token

204.55.38.png

Json Web Tokenを使用して認証処理をGo言語で行います。

まず認証用の鍵を作成します。

openssl genrsa -out demo.rsa 1024

openssl rsa -in demo.rsa -pubout > demo.rsa.pub

これで認証用の鍵を作成できました。

この鍵を認証を行なうコードと同一のフォルダにおきます。

認証処理は下記のコードになります。

2つの関数で構成されています。


  1. JWTトークンを取得して、値の整合性を判定する部分

  2. 公開鍵を読み込む部分



func CheckJWTHandler(api_token string) (bool){
tokenString := api_token
if tokenString == "" {
return false
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return LookupPublicKey()
})
if err != nil || !token.Valid {
fmt.Println("Failed")
return false
}
return true
}
func LookupPublicKey() (*rsa.PublicKey, error) {
prev, err := filepath.Abs(".")
if err != nil {
defer fmt.Println("Error")
}
key, _ := ioutil.ReadFile(prev + "/demo.rsa.pub")
parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key)
return parsedKey, err
}

1の部分がややこしいので説明すると

下記のように”#_#”に置き換えます

jwt.Parse(tokenString, #_#)

そうすると取得したトークンと*の部分をParseして比較していることが分ります。

*を展開していきます。

jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {#_#})

ある関数から値を取得していることが分ります。

メソッドレシーバーとしてtoken *jwt.Tokenを使用しています。

戻り値としてinterfaceerrorを取得することが目的です。

jwt.Parse(tokenString, 

func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return LookupPublicKey()
}
)

メソッドレシーバーを使用してtoken.Method.(*jwt.SigningMethodRSA)の部分でトークン認証用のメソッドとして正しいかをチェックしています。

正しければreturn LookupPublicKey()公開鍵を読み込んで、interface{}, errorに返しています。

実際に認証のトークンを作成する場合は下記のサイトのDebuggerを選択し、RS256を選択すると作成されるトークンの例が確認できるので、そこから先ほど生成した公開鍵と秘密鍵のデータを登録すればJWTトークンが作成できます。

https://jwt.io/

Json Web Tokenの具体的な使用方法は下記をご覧下さい

https://jwt.io/introduction/

時刻情報を付与したい場合は下記を使用すると簡単にunix timeが取得できます。

http://url-c.com/tc/

参考

GoでJWT認証するAPI Gatewayを作成する

RS256認証の鍵作成コマンド

ssh-keygen -t rsa -b 4096 -f jwtRS256.key

# Don't add passphrase
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
cat jwtRS256.key
cat jwtRS256.key.pub


DeveLop

Screen Shot 2016-03-01 at 6.26.13.jpg

ここからはVagrant、Docker、CircleCiを使用した開発環境構築について記述していきます。

Go言語ならクロスコンパイラだし、バイナリ一つだからそれはいらなくないという指摘はもっともなのですが、下記の理由でDockerを使用しました。


  1. 仕組み上、commitしないとコードの確認できないため、commit単位が細かくなる

  2. 作成した環境が明示的

  3. マルチコンテナによりwebサーバーとDBサーバーを仮想的に分離してテスト可能

今回の構成の参考は下記です。

figを使用しているため、そこはDocker-composeに置き換えてもらう方が良いです。

https://github.com/ahawkins/docker-project-template


Vagrant

Screen Shot 2016-03-01 at 6.26.13.jpg

Vagrantfileの中身です。

今回は参考にしたリンクで動作させようとするとエラーが出たので私の場合は下記のVagrantfileにしました。

特別な設定は特になく、


  1. ipアドレスを指定して、ウェブサーバーの起動をローカルでも確認できるようにしている

  2. メモリを明示的にいくつ確保するか指定している点(メモリが足りなくてエラーが起きる場合もあるので)

程度です。

# -*- mode: ruby -*-

# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

# Every Vagrant virtual environment requires a box to build off of.
config.vm.box = "お好きなlinuxディストリビューションを指定"

# Create a private network, which allows host-only access to the machine
# using a specific IP.
config.vm.network "private_network", ip: "指定したいIPアドレス"

# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
config.vm.provider "virtualbox" do |vb|
# Use VBoxManage to customize the VM. For example to change memory:
vb.customize ["modifyvm", :id, "--memory", "必要そうなメモリ数"]
end
end

このvagrantfileを使用して

vagrant up

vagrant ssh

を行なえばvagrantの環境に入れます。

vagrantの環境に入ったら下記コマンドを実行して下さい。

git clone https://github.com/ahawkins/docker-project-template.git


Docker

Screen Shot 2016-03-01 at 6.26.13.jpg

私の場合はVagrant環境をUbuntuで構成したので下記リンクからVagrant上にDocker環境を構築しました。

UbuntuへのDockerインストール

通常のインストールではスーパーユーザーでしかDockerが使用できないので下記のコマンドで権限を与えた方がベターです。

sudo usermod -aG docker ユーザー名

今回はマルチコンテナを使用するのでDocker composeも必要になります。

下記コマンドを入力するとすぐにインストールできます。

バージョンは目的に応じて、指定して下さい。個人的には最新のバージョンで良いかと思います。

curl -L https://github.com/docker/compose/releases/download/バージョンを指定/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

参考

https://docs.docker.com/compose/install/


Dockerfile

Dockerfileでgo言語の環境設定を行います。

先ほどgit cloneしたdocker-project-templateの中に/dockerfiles/testファイルがあります。

これがDockerfileになっています。

FROM ubuntu:14.04

RUN apt-get update && \
apt-get install -y build-essential mercurial git subversion wget curl vim iptables sqlite3 libsqlite3-dev mysql-server libmysqld-dev rsync

# go tarball
RUN wget -qO- http://golang.org/dl/goのバージョン指定.linux-amd64.tar.gz | tar -C /usr/local -xzf -

# GOPATH
RUN mkdir -p /goprojects && \
mkdir -p /goprojects/bin && \
mkdir -p /goprojects/pkg && \
mkdir -p /goprojects/src && \
mkdir -p /goprojects/src/github.com
RUN mkdir /root/.ssh && chmod 700 /root/.ssh

# If you use the git lab, you have to need the below command
COPY id_rsa /root/.ssh/
COPY id_rsa.pub /root/.ssh/
RUN ssh-keyscan -t rsa gitlab.com >> /root/.ssh/known_hosts
RUN chmod 600 /root/.ssh/id_rsa

RUN cd /goprojects/src/github.com && \
git clone 自分のgithubもしくはgitlabのリンクを指定
RUN cd /goprojects/src/github.com/クローンしたフォルダ && \
git checkout -b ローカルブランチ リモートブランチ

# env vars

ENV GOPATH GOPATHを指定したいフォルダを指定
ENV PATH クローンしたフォルダ内に含まれるbinフォルダを追記:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

RUN go get github.com/revel/revel && \
go get github.com/revel/cmd/revel && \
go get github.com/dgrijalva/jwt-go && \
go get github.com/go-gorp/gorp && \
go get github.com/mattn/go-sqlite3 && \
go get golang.org/x/crypto/bcrypt && \
go get github.com/go-sql-driver/mysql

RUN git clone https://github.com/revel/samples.git $GOPATH/src/github.com/revel/samples
# 毎回pull処理を実行するために必要
ADD date.txt /data/
RUN cd /goprojects/src/github.com/クローンしたフォルダ && \
git pull

# COPY key
COPY demo.rsa.pub クローンしたフォルダのcontrallerにコピー

WORKDIR クローンしたフォルダをデフォルトのワーキングディレクトリに設定
RUN cd クローンしたcontrallerのフォルダに移動 && \
revel test 作成したrevel名 dev

適宜、必要な部分は読者の環境に合わせて記述し直して下さい。

私がハマった所を記述しておくと

下記のようにhostを登録しておかないとgitlabではhost認証時にエラーが出ます。

RUN ssh-keyscan -t rsa gitlab.com >> /root/.ssh/known_hosts

下記のコマンドは一見不要ですが、dockerはcacheの機能があるため、同じ操作はしてくれません。

これは高速化のために非常に有用なのですが、git pullを使用する際は困ってしまいます。

そこで小手先の技術ですが、dateコマンドで毎回中身が変わるようなファイルを作成して、それをADDすることで、それ以降の処理のcache機能を無効化して処理を行なっています。

ADD date.txt /data/


ServerSpec

Screen Shot 2016-03-01 at 6.26.13.jpg

ServerSpecによってDocker環境が適切に設定されているか確認しましょう。

ServerSpecを動作させるためにローカルホストには下記の設定が必要です。

rubyの設定を下記のように行ないます。

sudo apt-get install -y rake 

curl -sSL https://rvm.io/mpapis.asc | gpg --import -

curl -L get.rvm.io | bash -s stable

source ~/.profile

source ~/.rvm/scripts/rvm
#上記でrvmが動作しない場合
source /etc/profile.d/rvm.sh

rvm install ruby-2.2.2

gem install rake
gem install docker-api
gem install serverspec

spec_helper.rbの設定を行なってDocker環境をチェック出来るように設定します。

# coding : utf-8

require 'serverspec'
require 'docker'

#backend docker
set :backend, :docker
# docker_url setting DOCKER_HOST
set :docker_url, ENV["DOCKER_HOST"]

# SSL
Excon.defaults[:ssl_verify_peer] = false

後は通常のServerSpecと同様の処理なので下記を参照して環境チェックようのrubyを記述して頂けると環境チェックが行なえます。

【Docker】Serverspecを用いたDockerのテスト

serverspec 公式リファレンス

少し工夫した点だけ抜粋しておきます。

下記でDockerの設定を行なうのですが、Dockerのキャッシュが設定されていない場合に時間が遅く、タイムアウトエラーで処理できないことがあります。

それを防ぐためにdefaultのタイムアウトの時間を遅く設定することでキャッシュがなくて動作が遅くても動作させています。


before(:all) do
# Seting Current Directory Dockerfile
Excon.defaults[:write_timeout] = 1000
Excon.defaults[:read_timeout] = 1000
image = Docker::Image.build_from_dir('.')
# Setting OS
set :os, family: :ubuntu
# docker image
set :docker_image, image.id
end

def os_version
command('cat /etc/issue').stdout
end

次に同様のチェック処理が入るときは配列を用意してチェックしています。


array = Array['git', 'wget', 'curl', 'vim', 'sqlite3', 'mysql', 'rsync']
for var in array do
it 'install check ' + var do
expect(command('ls -la /usr/bin').stdout).to match(var)
expect(file('/usr/bin/' + var)).to be_executable
expect(command(var + ' --version').stdout).to_not be nil
end
end

権限のチェック部分です。公式リファレンスだけだと私は分りにくかったので一応、載せておきます。

  it 'mode check id_rsa' do

expect(file('/root/.ssh/id_rsa')).to be_mode 600
end


Docker compose

今回は開発環境をマルチコンテナでDBサーバーコンテナとWEbサーバーコンテナを用意する必要があります。

そのため下記のようなDocker compose用のymlを用意します。

mysql:

image: mysql
ports:
- "3306:3306"
volumes:
- myconf.d:/etc/mysql/conf.d
environment:
MYSQL_DATABASE: "データベース名"
MYSQL_ROOT_PASSWORD: "パスワード"
MYSQL_USER: "ユーザー名"
MYSQL_PASSWORD: "ユーザーパスワード"
web:
build: .
restart: always
links:
- mysql
ports:
- '80:9000'

このymlを用意して実行することでmysqlのコンテナが立ち上がり、mysqlのコンテナにひも付けらた形でdockerのコンテナも立ち上がります。


Go言語のrevelの設定

ここで一旦Go言語のrevelに戻って頂く必要があります。

DBを繋ぐための設定をrevel側でする必要があります。

app.confというファイルにその内容を記述します。

[dev]と[prod]で開発環境とdeploy後の環境を使い分けるように記述します。

[dev]

mode.dev=true
watch=true
module.testrunner=github.com/revel/modules/testrunner
db.uri = "vagrant上でifconfigで確認できるeth0のipアドレス:3306"
db.name = "データベース名"

[prod]
watch=false
module.testrunner=
mode.dev = false

db.uri = "rdsのエンドポイント:3306"
db.name = "データベース名"


CircleCi

Screen Shot 2016-03-01 at 6.26.13.jpg

ここまでで環境構築のための準備が終わりました。

CircleCiによって継続的CIができるようにしましょう。

今回、使用するレポジトリにはすでにcircleciのための準備がされているので、ほとんどする作業はありません。

circleci.ymlの中身を確認します。

machine:

services:
- docker

dependencies:
pre:
- curl -L https://github.com/docker/fig/releases/download/1.0.0/fig-`uname -s`-`uname -m` > ~/bin/fig
- chmod +x ~/bin/fig
- make pull -j 4
- make environment
- make build

test:
override:
- make test-ci

今回はfigを使用しないのでfigの部分を無視すると実質的にmake test-ciの処理を行なっているだけになります。

Makefileにtest-ciの処理が記述されているので、それを今回用の処理に書き換えます。

今回はdocker composeを使用してマルチコンテナを実現するので下記を書き換えます。

変更前

DOCKER_RUN:=docker run -it $(foreach link,$(LINKS),--link $(REPO_NAME)_$(link)_1:$(link))

:
:
test-ci: images/tests
$(DOCKER_RUN) $(REPO_NAME)/tests

変更後

DOCKER_RUN:=docker run -i -t $(REPO_NAME)/tests:$(LINKS)  bash

:
:
test-ci: images/tests
$(DOCKER_RUN)

単純に実行するだけに書き換えました。

今回はgit pullを毎回実行するためにdateコマンドでファイルの書き換えを行なう必要があるため実行する時は下記のようなコマンドになります。

date > date.txt && make test-ci


Docker Hub

コンテナのテストとアプリのテストが出来たコンテナのイメージをDocker hubに移しておきます。

Docker Hubの使用方法は下記をご覧下さい

https://docs.docker.com/v1.8/userguide/dockerrepos/

Docker hubを使用することで環境設定とアプリケーションがテストされたコンテナを共有することができます。


AWS

ここからはAWS環境における継続的インテグレーションの実現のための内容になります。

もう一度、図を見てここから行なう作業部分を確認してみましょう。

Screen Shot 2016-03-01 at 6.26.13.jpg

TerraformでAWSのインスタンスを設定して立ち上げて、そこからAnsibleで環境設定を行なう流れです。


Terraform

Screen Shot 2016-03-01 at 6.26.13.jpg

まずterraformで環境構築を行ないます。

terraform初心者の方は下記をご覧下さい。

Step by stepで学ぶTerraformによる監視付きAWS構築

今回もsampleを参考に実装します。

AWS-Auto Scale Group

AWS-RDS

このサンプルはほとんど変える必要はなく、

AWS-Auto Scale Groupのvariables.tfのみ変更したら良いです。

環境によって変わる可能性がありますが、私のケースではus-east-1dを削除したら動作しました。

変更前

variable "availability_zones" {

default = "us-east-1b,us-east-1c,us-east-1d,us-east-1e"
description = "List of availability zones, use AWS CLI to find your "
}

変更後

variable "availability_zones" {

default = "us-east-1b,us-east-1c,us-east-1e"
description = "List of availability zones, use AWS CLI to find your "
}

あとは基本的なキーの登録を行なっておけば動作します。

キーの登録などが分らない方はStep by stepで学ぶTerraformによる監視付きAWS構築はご覧下さい。

キーの作成で注意する点はリージョンです。

us-eastのリージョンで作成しないと動作しないので注意して下さい。

キーについては一つ罠があります。

例えば"hoge.pem"の鍵を登録する場合は下記のように"pem"を外して登録する必要があります。

variable "key_name" {

description = "Name of the SSH keypair to use in AWS."
default = "hoge"
}


Ansible

Screen Shot 2016-03-01 at 6.26.13.jpg

Ansibleの初心者は公式ドキュメントと下記をご覧になってから読まれた方が良いです。

インフラ自動構築エンジン "Ansible"の勘所を1日でつかむ ~基礎入門編~

Ansibleについてですが、Dockerの設定のみを行い、Docker hubから必要なコンテナを取得するようにします。

こうすることで環境差が生まれず環境設定とアプリケーションがテストされた環境をAWS上に実現できます。

ansibleで最初にはまる部分は再起動の部分になるので、その部分は記述しておくので良かったら参考にしてください。

task用

- name: Restart the server

shell: reboot now
sudo: yes

- name: Wait until the virtual machine stop ssh port stop responding
local_action: wait_for host={{ inventory_hostname }} port=22 state=stopped
sudo: false

- name: Wait for server to come up
local_action: wait_for host={{ inventory_hostname }} port=22 delay=30
sudo: false

inventory_hostname変数を設定する用

vars設定用

inventory_hostname: 対象のサーバーのipアドレス

参考

http://d.hatena.ne.jp/incarose86/20150215/1424017177


総括

現状、流行っている技術を一通り抑えれたので良かったと思いますが、本来あるべき姿はなぜこの技術を使用するか学習コストは見合っているのかを考えてやるべきです。

今回の枠組みで最も良いと思ったのがcommitメッセージを細かくする環境にしたことです。それによりcommitの単位が細かくなり、commitメッセージの書き方にそって書くようにすると考える癖が自然につき、良い習慣になると思いました。


何か仕事をしたり興味あるものを見つけたとき、

簡単にあきらめないで「もう少し考えよう」

もう一工夫してみよう」と意識的に

自分を駆り立てることが重要である。

簡単なことではないが。

磯貝芳郎


この言葉を旨に工夫できる点を見つけては取り入れていこうと思います。