まっしろけっけ

めもてきなやーつ

k8s に Datadog を導入して APM のみ有効にしたい

はじめに

www.datadoghq.com

Datadog とはサーバのモニタリングなどを行ってくれるサービス。
一般的なメトリクスのモニタリング以外にも log を集約したりなど様々な機能があり、設定次第でそれぞれを有効化/無効化できます。

で今回はメトリクスのモニタリングは別でやってるので APM だけ有効にしたいんだよね〜ということをやっていきます。

Datadog 導入

まず Datadog には agent というものが存在します。
この agent が Datadog の server にメトリクスの情報を送るなどを行ってくれるものになります。

NewRelic などは gem として入れた agent が直接情報を NewRelic の server に送信しますが、Datadog は agent を別でインストールし、それに加えて gem も入れるということになります。この gem が agent に情報を送り agent が server に情報を送るという構成になっています。

APM としての機能だけではなく、server のメトリクス収集なども行ってくれるのでおそらくこういう構成になっているんでしょうね。

kubernetes に agent を install

導入方法はマニュアルの ここ に書いてありますが、k8s 上では daemonset として agent を deploy します。(見てもらえればわかりますが helm を使った install 方法なども書いてあります)

$ kubectl apply -f "https://raw.githubusercontent.com/DataDog/datadog-agent/master/Dockerfiles/manifests/rbac/clusterrole.yaml"

$ kubectl apply -f "https://raw.githubusercontent.com/DataDog/datadog-agent/master/Dockerfiles/manifests/rbac/serviceaccount.yaml"

$ kubectl apply -f "https://raw.githubusercontent.com/DataDog/datadog-agent/master/Dockerfiles/manifests/rbac/clusterrolebinding.yaml"

必要な RBAC を設定していきます。

次に API key の secret を作成

$ kubectl create secret generic datadog-agent --from-literal api-key="<DATADOG_API_KEY>" --namespace="default"

最後に daemonset を apply

$ cat daemonset.yaml
---
# Source: datadog/templates/install_info-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: datadog-agent-installinfo
  labels: {}
  annotations:
    checksum/install_info: 9f58f4fe71f1b79dfabae5311eb8f5373bd03072d4636e248cd3f581f8752627
data:
  install_info: |
    ---
    install_method:
      tool: kubernetes sample manifests
      tool_version: kubernetes sample manifests
      installer_version: kubernetes sample manifests
---
# Source: datadog/templates/daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: datadog-agent
  labels: {}
spec:
  selector:
    matchLabels:
      app: datadog-agent
  template:
    metadata:
      labels:
        app: datadog-agent
      name: datadog-agent
      annotations: {}
    spec:
      containers:
        - name: agent
          image: "gcr.io/datadoghq/agent:7.23.1"
          imagePullPolicy: IfNotPresent
          command: ["agent", "run"]
          resources: {}
          ports:
            - containerPort: 8125
              name: dogstatsdport
              protocol: UDP
          env:
            - name: DD_API_KEY
              valueFrom:
                secretKeyRef:
                  name: "datadog-agent"
                  key: api-key
            - name: DD_KUBERNETES_KUBELET_HOST
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
            - name: KUBERNETES
              value: "yes"
            - name: DOCKER_HOST
              value: unix:///host/var/run/docker.sock
            - name: DD_LOG_LEVEL
              value: "INFO"
            - name: DD_DOGSTATSD_PORT
              value: "8125"
            - name: DD_APM_ENABLED
              value: "false"
            - name: DD_LOGS_ENABLED
              value: "false"
            - name: DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL
              value: "false"
            - name: DD_LOGS_CONFIG_K8S_CONTAINER_USE_FILE
              value: "true"
            - name: DD_HEALTH_PORT
              value: "5555"
            - name: DD_ENABLE_PAYLOADS_EVENTS
              value: "false"
            - name: DD_ENABLE_PAYLOADS_SERIES
              value: "false"
            - name: DD_ENABLE_PAYLOADS_SERVICE_CHECKS
              value: "false"
            - name: DD_ENABLE_PAYLOADS_SKETCHES
              value: "false"
            - name: DD_PROCESS_AGENT_ENABLED
              value: "false"
          volumeMounts:
            - name: installinfo
              subPath: install_info
              mountPath: /etc/datadog-agent/install_info
              readOnly: true
            - name: config
              mountPath: /etc/datadog-agent
            - name: runtimesocketdir
              mountPath: /host/var/run
              mountPropagation: None
              readOnly: true
            - name: procdir
              mountPath: /host/proc
              mountPropagation: None
              readOnly: true
            - name: cgroups
              mountPath: /host/sys/fs/cgroup
              mountPropagation: None
              readOnly: true
          livenessProbe:
            failureThreshold: 6
            httpGet:
              path: /live
              port: 5555
              scheme: HTTP
            initialDelaySeconds: 15
            periodSeconds: 15
            successThreshold: 1
            timeoutSeconds: 5
          readinessProbe:
            failureThreshold: 6
            httpGet:
              path: /ready
              port: 5555
              scheme: HTTP
            initialDelaySeconds: 15
            periodSeconds: 15
            successThreshold: 1
            timeoutSeconds: 5
        - name: trace-agent
          image: "gcr.io/datadoghq/agent:7.23.1"
          imagePullPolicy: IfNotPresent
          command: ["trace-agent", "-config=/etc/datadog-agent/datadog.yaml"]
          resources: {}
          ports:
            - containerPort: 8126
              hostPort: 8126
              name: traceport
              protocol: TCP
          env:
            - name: DD_API_KEY
              valueFrom:
                secretKeyRef:
                  name: "datadog-agent"
                  key: api-key
            - name: DD_KUBERNETES_KUBELET_HOST
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
            - name: KUBERNETES
              value: "yes"
            - name: DOCKER_HOST
              value: unix:///host/var/run/docker.sock
            - name: DD_LOG_LEVEL
              value: "INFO"
            - name: DD_APM_ENABLED
              value: "true"
            - name: DD_APM_NON_LOCAL_TRAFFIC
              value: "true"
            - name: DD_APM_RECEIVER_PORT
              value: "8126"
          volumeMounts:
            - name: config
              mountPath: /etc/datadog-agent
            - name: runtimesocketdir
              mountPath: /host/var/run
              mountPropagation: None
              readOnly: true
          livenessProbe:
            initialDelaySeconds: 15
            periodSeconds: 15
            tcpSocket:
              port: 8126
            timeoutSeconds: 5
        - name: process-agent
          image: "gcr.io/datadoghq/agent:7.23.1"
          imagePullPolicy: IfNotPresent
          command: ["process-agent", "-config=/etc/datadog-agent/datadog.yaml"]
          resources: {}
          env:
            - name: DD_API_KEY
              valueFrom:
                secretKeyRef:
                  name: "datadog-agent"
                  key: api-key
            - name: DD_KUBERNETES_KUBELET_HOST
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
            - name: KUBERNETES
              value: "yes"
            - name: DOCKER_HOST
              value: unix:///host/var/run/docker.sock
            - name: DD_LOG_LEVEL
              value: "INFO"
            - name: DD_ORCHESTRATOR_EXPLORER_ENABLED
              value: "false"
          volumeMounts:
            - name: config
              mountPath: /etc/datadog-agent
            - name: runtimesocketdir
              mountPath: /host/var/run
              mountPropagation: None
              readOnly: true
            - name: cgroups
              mountPath: /host/sys/fs/cgroup
              mountPropagation: None
              readOnly: true
            - name: passwd
              mountPath: /etc/passwd
            - name: procdir
              mountPath: /host/proc
              mountPropagation: None
              readOnly: true
      initContainers:
        - name: init-volume
          image: "gcr.io/datadoghq/agent:7.23.1"
          imagePullPolicy: IfNotPresent
          command: ["bash", "-c"]
          args:
            - cp -r /etc/datadog-agent /opt
          volumeMounts:
            - name: config
              mountPath: /opt/datadog-agent
          resources: {}
        - name: init-config
          image: "gcr.io/datadoghq/agent:7.23.1"
          imagePullPolicy: IfNotPresent
          command: ["bash", "-c"]
          args:
            - for script in $(find /etc/cont-init.d/ -type f -name '*.sh' | sort) ; do bash $script ; done
          volumeMounts:
            - name: config
              mountPath: /etc/datadog-agent
            - name: procdir
              mountPath: /host/proc
              mountPropagation: None
              readOnly: true
            - name: runtimesocketdir
              mountPath: /host/var/run
              mountPropagation: None
              readOnly: true
          env:
            - name: DD_API_KEY
              valueFrom:
                secretKeyRef:
                  name: "datadog-agent"
                  key: api-key
            - name: DD_KUBERNETES_KUBELET_HOST
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
            - name: KUBERNETES
              value: "yes"
            - name: DOCKER_HOST
              value: unix:///host/var/run/docker.sock
          resources: {}
      volumes:
        - name: installinfo
          configMap:
            name: datadog-agent-installinfo
        - name: config
          emptyDir: {}
        - hostPath:
            path: /var/run
          name: runtimesocketdir
        - hostPath:
            path: /proc
          name: procdir
        - hostPath:
            path: /sys/fs/cgroup
          name: cgroups
        - name: s6-run
          emptyDir: {}
        - hostPath:
            path: /etc/passwd
          name: passwd
      tolerations:
      affinity: {}
      serviceAccountName: "datadog-agent"
      nodeSelector:
        kubernetes.io/os: linux
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 10%
    type: RollingUpdate

# Source: datadog/templates/containers-common-env.yaml
# The purpose of this template is to define a minimal set of environment
# variables required to operate dedicated containers in the daemonset

$ kubectl apply -f daemonset.yaml

元々の daemonset.yaml と比べてもらえるとわかりますがいくつかの環境変数を設定することで APM のみを有効にしています。

gem を install

今回は Rails のアプリケーションに APM を入れるので Gemfile に書いていきます。

$ vi Gemfile
+ gem 'ddtrace'
$ bundle install
$ vi config/initializers/datadog.rb
Datadog.configure do |c|
  c.tracer enabled: true
  c.use :rails, service_name: "hoge"
end

この状態でリリースしてあげると Datadog の画面上で APM の情報が見れるようになると思います。

最後に

APM のみ有効にしてみましたが、導入して一ヶ月経っていないので支払いがまだ発生しておらず APM のみになっているか不安ではあるのですが Usage の画面を見ると APM の情報のみしか出ないのでおそらく成功していると思われます。

Pepabo Tech Conference #13 に登壇

はじめに

pepabo.connpass.com

こちらに登壇しました。

登壇内容

speakerdeck.com

ここ数年で主に自分がやってきているアーキテクチャの変更について一部を紹介したのと来年に向けてのやっていきを話た。

来年に関しては若者が色々頑張ってくれているので僕がメインで開発を進めることに関しては話してないのだけれど、色々とやっていく予定

さいごに

前日から犬の体調が優れず、大変だったのですが無事に終わって良かった...(犬のだいぶ回復してきた)

食べるものを変えてみた

はじめに

在宅勤務になり基本家でご飯を食べることが多くなりました。
元々平日夜は自炊をしていました。
基本的に同じものをずっと食べ続けることができるので同じものを作っていたのですが多少作るのがめんどくさいなぁ〜などと思いつつも 1 食 300 ~ 400円くらいで作れるし節約にはなるしなぁと思いやっていたんですが元同僚の ひさいちくんつくりおき.jp について tweet してるのを見て良さそうかもと思い利用し始めたので before/after を書いておく

before

朝飯

元々朝飯は食べておらず火曜日と木曜日に朝ジムにいくようになりジム後はお腹が空くのでコンビニでおにぎりを買うなどしていたが 1.5 ヶ月ほど前からプロテインに切り替えた。
これはつくりおき.jpの件とは関係なくなんとなくプロテイン飲んでみるか?となって

昼飯

昼飯はだいたいコンビニで弁当なりを買って食べていたので、毎日昼の時間になるとコンビニに買いに行っていた。
コンビニに行くと弁当だけではなくなんか余計なものも買っちゃうよね...というのと減量したいときはサラダと低カロリーのものだったりを買うので割と 1000 円いかないくらいかかってしまっていた。

夕飯

夏場はあまり火を使いたくないのでオーブンレンジを活用していた。
何種類かの野菜と鶏肉を適当な大きさに切って袋に入れてそこにオリーブオイルとその日の気分で塩や醤油などを入れて全体になじむように揉んだ後にオーブン用の鉄板にクッキングシートをしいてそれの上に先のものを並べてオーブンで 30 分放置するだけでそれなりに美味しいものができる。

冬場はずっと鍋を食べている。スープは市販のものではなく粉末のかつおだしや鶏だしをベースに塩や醤油などで薄味にしていた。一回で 2,3 日分をまとめて作って残ったやつはそのまま冷蔵庫行きにしているので毎日料理する必要がなくて楽。


基本的には週の初めにその週使う野菜と肉は切っておくので週の途中で調理を行う時間を短縮していた

両方とも野菜が取りやすいし、塩分なども控えめにできるので健康的にもいいかなという感じ。
基本的に夕飯に関しては米は食べないようにしていたのでこれで終わり。

after

朝飯

特に変わらずジムに行った後だけプロテインを飲んでいる。

昼飯

つくりおき.jpから送られてきた料理を皿に取り分けてレンジでチンするだけ、ご飯は数年ぶりに稼働させた炊飯器で炊いている。
米に関してはロウカット米なるものを購入してそれに十六穀を混ぜて食べているが普通にうまい。

夕飯

夕飯も昼飯と同じでつくりおき.jpから送られてきた料理を取り分けてレンジでチンするだけ。
それだけだと足りないので夕飯も米を食べるようにした。

最後に

こんな感じでつくりおき.jpを利用しだしたことによって平日は完全に料理をしなくなり時間が増えた。
今までも休日にそれなりに手の込んだものだったりを作ったりしてたのでそれは変わらず。

つくりおき.jpは週 3 食プランで合計で 12 食届くので月 ~ 金と土日のどちらかの昼飯と夕飯という感じで食べている。
金額的にも一食 650 円で1日なら * 2 で 1300 円ほどになるので以前の料理をしていた時と比べてもすごく差があるという感じではない。
味はすごく美味しいし、料理の種類も自分では作らんだろうなって感じのものが多いのですごく嬉しいし毎日ほぼ同じものを食べていた以前と比べると今日はどれを食べようか?という楽しみができた。

nosh も試しはしたがメインは良いとして付け合わせの野菜などがやはり冷凍感があってそこがつくりおき.jpと比べると劣るなって感じだった。

体重的な変化だと 10 年以上ぶりに家で夕飯に米を食べ出したが体重に大きな変化はないというか緩やかに落ちている感じもある
コンビニで余計なものを買う機会が減ったからかもしれない...

炊飯器は 10 年以上前に 3000 円くらいで買ったやつなので買い換えようと思っている

EKS の node を managed node groups 管理に移行する

はじめに

aws.amazon.com

EKS の managed node groups にカスタム AMI と EC2 起動テンプレートのサポートが追加されました。
元々 managed node groups が出る以前から EKS を使用しておりかつカスタム AMI も使用していたので managed node groups が登場した当初に試した時はカスタム AMI 使えないならダメじゃんとなり移行を諦めましたが、サポートされたことにより移行するぞとなりました。

managed node groups を使わない場合の問題

問題というほどではありませんが、managed node groups を使わない場合 CloudFormation で node を定義して cluster にアタッチするような手法だったので EKS のコンソールを見ても node との繋がりがわかりにくいという問題がありました。
`k get nodes` で node 一覧を取得するか EC2 インスタンスの特定のタグで絞込みをすることで EKS の node なのだと把握できるくらいでしょうか?(それでも大きな問題はないんですけど)

手順

EKS cluster の作成

https://console.aws.amazon.com/eks/home#/clusters こちらからクラスター作成を選択

f:id:shiro-16:20200828115940p:plain

cluster 名とかは適当に入れます。今回は shiro16-test としています。 kubernetes の version はお好きなものを選びます。今回は最新が最高ということで選べる最新の 1.17 にしています。
クラスターサービスロールはこちらを参考に IAM ロールを作成しそれを選択します。

f:id:shiro-16:20200828115945p:plain

続いてのネットワーク周りの設定は自身の運用環境に合わせて設定してください。

f:id:shiro-16:20200828115954p:plain

ログ周りも運用に合わせて設定してください。本番稼働なのであれば全て取得しておくのが良いかもしれません。
これで入力することはほぼ終わりなので確認画面で作成をポチッとすると cluster が作成されます。(10min ほど完了まで時間がかかるかと思われます)

managed node groups 作成

EKS cluster の作成が完了し cluster の詳細画面にいくと下記のような画面が表示されていると思います。

f:id:shiro-16:20200828120005p:plain

ここでノードグループの追加を選択します。

f:id:shiro-16:20200828120020p:plain

ノードグループ名は好きな名前を入力し、ノード IAM ロールはこちらを参考に IAM ロールを作成しそれを選択します。

更に今回はカスタム AMI を使用するので起動テンプレートを使用するを選択します。
起動テンプレートの設定に関しては後述しますが、強力にキャッシュしているのかここの選択に新規で作成したテンプレートやバージョンがリロードしても表示されないことがありますが、再読み込みマークを選択すると表示されるはずです。

f:id:shiro-16:20200828120027p:plain

最後に node 数を決めて作成を開始すれば EC2 インスタンスが立ち上がり node が cluster に join してくれます。

EC2 起動テンプレート作成

https://console.aws.amazon.com/ec2/home#LaunchTemplates こちらのページで起動テンプレート作成を選択します。
起動テンプレート名や説明を入力します。次に下記のような項目があるので使用したい AMI とインスタンスタイプを選択します。

f:id:shiro-16:20200828120024p:plain

ちなみに仕事では packer + ansible でこちらの AMI に wazuh agent や stns 等をインストールした AMI を作成しています。

最後に高度な詳細 という箇所があるのでそこを開いて一番下のユーザーデータの部分にこのようなスクリプトを書きます。

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: text/x-shellscript; charset="us-ascii"
#!/bin/bash
set -ex
B64_CLUSTER_CA=xxxxxxxxxxxxxxx
API_SERVER_URL=https://xxxxxxxxxxxx.xx.region.eks.amazonaws.com
/etc/eks/bootstrap.sh shiro16-test --kubelet-extra-args '--node-labels=eks.amazonaws.com/nodegroup=shiro16-test-nodegroup,eks.amazonaws.com/nodegroup-image=ami-xxxx' --b64-cluster-ca $B64_CLUSTER_CA --apiserver-endpoint $API_SERVER_URL

--//--

shiro16-test は自身の cluster 名
shiro16-test-nodegroup は node group 名
ami-xxxx は使用する AMI 名

B64_CLUSTER_CA と API_SERVER_URL に関しては下記のコマンドで取得可能です。

$ aws eks describe-cluster --name shiro16-test --query 'cluster.[endpoint,certificateAuthority]'
[
    "https://xxxxxxxxxxxx.xx.region.eks.amazonaws.com",
    {
        "data": "xxxxxxxxxxxxxxx"
    }
]

これで作成を行えば起動テンプレートの作成も完了です。

さいごに

AutoScale に関しては下記を見れば全てわかる

docs.aws.amazon.com


なぜ移行したではなくするなのかというと来週(8/31)から夏休みなのでその間に万が一何か起こるとだるいという理由だけです。
なので休み明けに移行します。

mackerel-agent を使って異常を検知した際に自動で復旧させる

はじめに

普段会社では監視ツールとして mackerel などを使ったりしているのですが、mackerel を使っている場合 nginx が動いている server のメモリ使用量が一定割合を超えたらエスカレが来るみたいなことをしていると思います。

こういう場合の対応は基本的に nginx を reload させてあげて終わりとなることがほとんどなのでエスカレの電話きてわざわざ人が対応するよりは自動化できた方がいいよね?という気持ちになるわけです。
でそれが mackerel-agent を使えばできますという話。

mackerel-agent の設定

まずは mackerel-agent の conf を用意します。

mackerel-plugin-memory-checker.conf

[plugin.checks.memory]
command = "/usr/local/bin/memory_checker"
action = { command = "bash -c '[ \"$MACKEREL_STATUS\" != \"OK\" ]' && systemctl reload nginx", user = "root" }

これは command が正常に終了しなかった際に action を実行するという意味です。

memory_checker の中身はこんな感じ

#!/usr/bin/env ruby

metrics = {}
File.foreach("/proc/meminfo") do |line|
  if md = line.match(/(MemTotal|MemAvailable):[\s]+([0-9]+)/)
    metrics[md[1]] = md[2].to_i
  end
end

available_percentage = metrics["MemAvailable"] / metrics["MemTotal"].to_f * 100

if available_percentage < 20
  puts "Memory WARNING: #{available_percentage}%"
  exit 1
else
  puts "Memory OK: #{available_percentage}%"
end

メモリの残り容量が 20% をきっていたら exit 1 で異常終了させるというコードになります。
これによって mackerel-agent は conf で記述した action 内のコマンドを実行してくれます。

さいごに

対応方法がほぼ固定化しているエスカレであれば mackerel-agent の機能などを使ってどんどん楽をしていくのが良いと思います。

深夜も平和に寝れるしね

高速に機能開発を行う際の思考

はじめに

僕自身は普段の開発において手が早いと言われることが多く、現職も含めて 3 社全てでそう言われてきたので実際にそうなのだろうなという気がしている。
見積もりをする際も「 n 時間(またはポイント)ですね」と答えると「じゃ他の人ならそれの 2 〜 3 倍くらいかな」みたいな会話をされたこともある。

何故自分は手が早いんだろうか?他の人との違いは何なのだろうか?というのを考えはじめたのだが、他人の頭の中を覗くのは不可能なので
一般的な機能開発に絞って自身がどのような思考で開発を行なっているのか?というのを考えてみた。(他の部分でもこういう思考をしているかもしれないが今回は機能開発の際にどういう思考になっているかしかまとめません)

要件定義

新しい機能を作る際はだいたい誰かしらが◯◯という課題を解決するために◯◯という機能を作りたいというのから始まり、本当にユーザが求めてるのはその機能なのか?という話だったりその機能で課題を解決できるのか?みたいな話をすると思うんですよ。この段階では特にそれについて考える以上のことはないので省略しますが、大事なのはこの後の機能を作る際の仕様を固めていく要件定義の段階。

結論から言うと要件定義が終わった段階で自身の頭の中には動くコードが既に出来上がっています。(自分の中ではコードが降って来たと表現している)

何故そのようなことになるかと言うと大事なのはパターンなのかなと。

何かの機能を作るとなった場合、ほとんどの場合(極)一部だけ同じような仕様や似たような仕様の場合がほとんどだと僕は思っていて、そういう過去に経験した似たパターンを組み合わせることによって新しい 1 つの機能を作ることが可能になると考えています。

例えば数値を入力させる場合上限と下限は幾つが良いのか?というのも要件定義に含まれると思いますが、このような入力に上限/下限を設けるのも一つのパターンとして捉えています。

このように細かいパターンを組み合わせることによって一つの機能が出来上がると捉えているので要件定義が終わった段階で僕の頭の中にはパターンが積み重なって出来た 1 つの機能が出来上がっているわけです。

さらにこのパターンは頭の中で実際のコードと紐づいているんですよね。先の数値の上限/下限であれば Validation が必要で Rails だと model に greater_than_or_equal_to などを使って記述するのような感じで。
なのでこのパターンが積み重なって 1 つの機能が出来上がった段階でそのパターンに紐付く実際のコードも出来上がるということになります。

全てが自身の頭の中にあるパターンに当てはまらないかもしれませんが、細かく分解すればほぼ当てはまるしもし当てはまらなくても当てはまらないということが発生することがまだ知らないことがあってそれを解決できるという楽しさに繋がっていると感じています。当てはまらない場合も機能全体が当てはまらないのではなくその機能の極一部なので他の部分のコードは既に頭の中では出来上がっているので当てはまらない部分を集中して考えるだけになります。

実際にコードを書く

コードを書く際は頭の中にあるコードをタイピングするだけなので、その際にも他のことを考えています。
例えば何かユーザにフォームから入力される場合は上記でまとめたパターンに漏れがないか?を頭の中で様々な入力に対してコードを走らせてみてみたり、仕様の漏れを探すということをすることが多い気がします。でこの仕様の漏れなどがあった場合はそれを test として書きどのような結果が適切なのかをコードに落とし込んでおく。

1 日で全てのコードを書き切れる程度の機能ならいいのですが、大きい機能だとそういう訳にもいかないので基本的に必ず機能単位でのキリが良いところで終わりにします。
これは翌日などに続きをするとなった際にどこまでやったっけ?と思い出すのを少なくするためです。
また、1 日で終わらない場合は翌日のコードを書き始める前に仕様をもう一度確認して頭の中にパターンを思い出させるということをやります。

コードを書く際にこれも大事なことだと思っているのですが、帰宅中や犬の散歩,お風呂などで書いたコードに対してリファクタリングできないか?をよく考える気がします。
タイピングしている際もここは DRY に出来るから module にまとめておくかみたいなことはよくあるのですが、一旦時間を置いて自身のコードと向き合うというのが重要かと思います。この際はビジネスロジックに関して考えるとこはせずあくまでリファクタリングに関してのみ考えています。(ビジネスロジックまで考えると仕事感が出すぎるので好きなリファクタだけを考えている)
これによってレビュー時の指摘なども減らせます。

その後

そのあとは実際に動作検証とかをしてリリースになると思うので特別考えていることはないですが、 test に書いてあることを何回も動作検証を行わないとか決済系以外はリリース後に問題(バグ)があってもまぁなんとか出来るだろうという考えでいるのでガンガンリリースしようという考えだという程度かなと思います。

さいごに

ということで機能開発を行う際にどのような思考で開発を行なっているか?をまとめてみた。
一文で表すと

パターンを蓄積しパターンを適切に配置、パターンと実際のコード(処理)を紐付けておくことによって高速に機能開発が出来ている

というのが僕が手が早いと言われる理由の一端かなともちろんこれが全てではないと思うので機能開発に関わらず書くかもしれない。


ちなみにこの記事は 7/15 に書いていたのですが同僚ライバルの pyama が同じようなテーマ(彼の場合は高速に開発するための自身の行動をどうするか?みたいな話)でブログを更新してたので公開を先延ばしにしました。

エンジニアとしての境界を超えることについて

はじめに

web service というものを開発するエンジニアには サーバサイド/インフラ/フロントエンド/iOS/Android などのそれぞれの専門(強み)を持ったエンジニアが存在していると思います。その専門性を境界として見た際に越境する/しないエンジニアではどのような違いがあるのかというのを自身の経験などから僕自身が考えている事をまとめていきます。

自身について

10 年以上お金を貰ってソフトウェアエンジニアをやっていて、その過程でサーバサイド/インフラ/フロントエンド/Android に関しての実務を一定期間行ってきたという経歴があります。現在は主にサーバサイド/インフラを中心として minne というサービスのシニアエンジニアリングリードというものをやっている。

書いたような領域以外でも DevOps みたいな領域の違いみたいなものもあると思いますが、僕自身 Dev と Ops に違いがあるというのは全然感じたことがなく Effective DevOps という書籍を読んでもふむ〜?となったし弊社の VPoE の @hsbt に 「DevOps 上手い」というような事を言われた記憶があるのだが(うろ覚え)なるほど(よくわかっていない)となった記憶がある。

越境する/しないの違い

上記で説明した通り僕自身は越境した側の人間でしてなかった時とした後で実際にどういう変化があったのか?というと細かくあげるといっぱいあるのだが大きいものを 2 点ほどあげる。他にも

1. それぞれの苦労が理解できる

それぞれの領域のエンジニアが、(特に)領域が繋がる部分でどのような事で苦労するか?やどのような状態なら嬉しいか?がわかるようになった。

例えば API のレスポンスで json を返す際に特定の値が基本的には文字列だが数字のみだったら int 型のような API だった場合にアプリエンジニアとしてはとても困るという事が発生するんですけど、iOSAndroid 側に越境した事がある開発者であればその事を理解できているはずなのでそもそもそんなレスポンスになる API は作らないという事になるはず。

この事によってリリース後に気づいて慌てて修正するだったり、リリース前の検証で発覚して修正をするというような手間が発生せず効率的に開発ができるという事になります。越境した事があるエンジニアがチームに 1 人いるだけでも仕様検討やレビューの段階でこのような事に指摘をしてくれるのでチームとしての生産性も向上すると思われます。

2. 各領域の話が理解できる

これは自分の現在の立場的によかったと感じるというのが大きいかもしれないのですが、シニアエンジニアリングリードというのは事業部のリードエンジニア兼エンジニアリングマネージャーなのでシステム全体に関して色々な事を考えたり時には各エンジニアに技術的な仕様について相談されたりという事があったり、部署のエンジニアを評価するという事が必要になってくる。

上記のような事があり、越境しているおかげでどういう事がしたいのか?だったり技術的にどんな高度な事をしているのか?を判断できるようになったという部分がある。

やってみたと実務の違い

それぞれの領域でとりあえずチュートリアルやって動くもの作りましたというのだけで越境したと捉えられる人もいるかもしれませんが、それだけでは継続的に開発をして得る事のできる知識とは雲泥の差がある為、実務にこだわらずともそれぞれの領域と関わる形(Android アプリなら API を利用する等)で継続的に開発を行ったことがあるという事を越境した事があると捉えています。

越境について

それじゃみんな越境すればいいのか?とか全ての技術を平均的にできたらいいのか?という話になると思うんですが、これに対する僕の考えを書いておくと

みんな越境すればいいのか?

これに対する僕の考えは(経験年数が浅い人ほど)興味があるならして欲しい。なぜかというと先に述べた 1 のメリットが大きいという考えからです。
また、越境した結果その分野が実はその人が一番楽しいと思える分野かもしれないからです。僕自身はサーバサイド/インフラから始まり一通り終えてサーバサイド/インフラがやっぱり楽しいと感じましたが、どこかでサーバサイド/インフラより面白いと感じるものがあったのならそこに留まっていたと思うし、仕事をするなら楽しいと思える事をやりたいじゃないですか。経験年数が浅い人ほどというのをつけたのは歳を取ると越境する体力みたいなのが徐々になくなってくるのかなぁと思うからです(個人の感想)

全ての技術を平均的にできたらいいの?

これ今回一番書きたかったことかもしれないんですが、僕自身としてはこの答えは no です。
時間は有限ですし、僕自身は全ての技術を高レベルで習得するというのは無理だと考えています。なので特定の技術領域に軸足を置きつつ他の領域のことも理解しているというのが良いと思います。(よく T 型と呼ばれるやつ)
一点だけを突き詰めていくんだという強い気持ちがある人はその限りではなくその一点を突き詰めていって欲しいですし。

なぜ上記のように考えるかというと僕のチームで働いている人には自身の市場価値を意識して欲しいなという考えがあって、僕のチームはそこまで多くないですがこの業界は転職する人も割と多くいる業界なので(勿論そうなって欲しくはないし、一緒に働けなくなるのは悲しいという前提はあるものの)チームメンバーが転職するとなる自体はある程度覚悟しているんですよ。

でその転職するとなったメンバーが一番入りたいと思った会社から採用されるエンジニアになって欲しいという思いがあるからで恐らく平均的にアプリからバックエンドまで出来るエンジニアと iOS アプリに関して深い理解があり技術的にも優れているエンジニアが iOS エンジニアのポジションに応募した場合に後者が採用される率の方が高いわけですよ。更にいうと iOS エンジニアとしての技量が同じくらいだった場合はその他の領域の事が少し出来るという人の方がチームとして働く上でプラスに捉えられる事が多いと思うのでそういう意味もあります。

全ての技術を平均的に出来るエンジニアというのはチームとしてはとても扱いやすいというのがあると思います。どこかの領域のリソースが足りなかったら今週はそこのタスクをやってもらって〜と動けるので...ただそういう便利屋さん的な人がいざ外に出るとなった際に便利屋さん的ポジションを大々的に求めている会社というのは少ないと思います。(スタートアップを除く、スタートアップでも T 型とかの方がいいのかなぁ?働いた事ないので知らない)
自ら望んでそういうエンジニアになるというのは良いと思いますが、強制的にそういうエンジニアにするというのは僕個人としてはその人のエンジニアとしての未来を潰しているという感じがしてしまうので行なっていません。

まとめ

長々と書きましたが、うちのチームでは基本的に「好きな(自分がやりたい)技術をやればいいよ」と言っており実際にどういうエンジニアになりたいか?は各々に任せています。

僕はメンバーが転職した際は転職先の人に強いエンジニアが来たって思って欲しいんですよね(勿論そうなって欲しくはないし、一緒に働けなくなるのは悲しいという前提はあるものの大事な事なので 2 回)

エンジニアとしての境界について書きましたが似たような事は他の職種間でも当てはまるのでは?と思います。

長文の日本語を書くのはまだまだ下手...