Hey kumamon

こんにちは、こんばんは。

指数表記についてメモ

指数表記とは数値の表記方法の一つである。

通常文字列が含まれた演算子を実行すると PHPの場合暗黙的な型変換が行われるので例えば以下のような出力結果がでる。

<?php

echo 10 + '5' . PHP_EOL;
=> 15 


echo 10 + '5a4' . PHP_EOL;
=> 15

2つ目の出力については、 文字列のなかにある数値を探し、数値が見つかったらそれ以降の文字列は 切り捨てられるため、10 + 5という演算が行われ、このような出力結果となる。

冒頭にもでてきた指数指標を演算を実行すると以下のような出力となる

<?php

echo 20 + '8.8E+2'.PHP_EOL; 
=>900

8.8E+2は880という数値を示しており、 880 + 20という計算になるため900という出力がされる。

指数表記は仮数部、基数、指数部の3つから成り立っていて 8.8が仮数部、10が基数、2が指数となり、 8.8 * 102 = 880 となる

8.8のあとはEなので切り捨てられるかと思いきや、指数関数として演算されるため 出力が異なるようだ。

interfaceと抽象クラス

オブジェクト指向を学んでると、出てくるinterfaceと抽象クラス。 自分でコードを書くときにどういったときに使うのかがわからないという声も少なくない。

そこでどういった場面で使うのか、使うことでのメリットをまとめてみた。

抽象化

抽象クラスのメリットを一言で言うならば、実装する上での決まりごとを決めるようなものだと思う。

<?php

abstract class Animal {
    
    abstract function cry(){}

}

class Dog extends Animal {

    public function cry() {
        echo "わん";
   }

}

class Cat extends Animal {

    public function cry() {
        echo "にゃーん";
   }

}

継承クラスであるAnimalクラスを継承したDog,CatクラスはそれぞれAnimalクラスで定義されているcryメソッドをオーバーライドしています。 一見するとわざわざ抽象クラスを定義してオーバーライドする意味がないように見えますが、 例えば自分以外の他の開発者がサブクラスとして、Pandaクラスや、Lionクラスを追加しようとしたときに 抽象クラスでない場合cryメソッドをオーバーライドしなくてもサブクラスを実装できてしまいます。

今回の例だと抽象クラスがあると、動物は基本鳴くということができるので、Animalクラスを継承するからには鳴くというメソッドを定義してくださいね という決まりごとを決めることができて、これにより、自分以外の他の開発者が抽象クラスを継承することにより、 実装しなければならないメソッドをうっかり実装し忘れたこということがなくなります。

抽象クラスのメリットが感じられるのは大規模な開発で、このような実装する上での約束を決めておけば、 実装するときに一貫性をもたせられるのが一番のメリットであるような気がする。 一人で開発するとき、または抽象クラスを継承し一貫性をもたせる必要がなければ無理に使う必要もないし 余計なコードが増えるので、使うときは今後のことを検討した上で使うべきだと思う。

インターフェース

インターフェースを使うのは疎結合のために使います。

<?php

abstract class IWalk  {
    public function wolking(string $walking) {}
}

class Human extends IWalk {
    
    public function working() {
        echo "二足歩行";
    }
    
}

class Dog extends IWalk {
    public function working() {
    echo "四足歩行";
    }
    
}


class Work {
    function __construct(IWalk $animal) {
        $animal->working();
    
    }

}

インターフェースを使うことで、異なるオブジェクトでも同一のオブジェクトであるかのように 扱えるようにできます。 Workクラスからするとどのオブジェクトを渡されても渡されたオブジェクトの中身を気にすることなく メソッドを呼び出せることができ、新しいオブジェクトを追加するときもインターフェースを使うことによって 呼び出し元のオブジェクトをわざわざ修正することもありません。 オブジェクト間での依存をなくすことで柔軟に耐えられるシステムを実現できる。

まとめ

抽象クラスはインターフェースと違い、メソッドを定義するだけでなく、実装することができる。 なので複数のサブクラスが抽象クラスを継承するときに共通の機能は抽象クラスで実装し、 あとはサブクラスで実装しなければならないメソッドを定義する。

アクセス修飾子もインターフェースがpublicのみに対して 抽象クラスはpublicとprotectedがある。

個人的な解釈としてはインターフェースと抽象クラスの違いは 抽象クラスは、共通の処理をまとめたりする中で使う人のためにあり、 インタフェースは、詳細は見せず出来ることを定義するものであるかなと思う。

アロー関数の{}

はじめに

例えばこんな感じの処理があるとします

const timeout = (time) => new Promise((resolve) => setTimeout(resolve, time));

Promise.resolve()
  .then( () => {
     timeout(1000)
  })
  .then( () => {
    console.log('おはよう')
  })
  .then( () => {
     timeout(1000)
  })
  .then( () => {
    console.log('こんにちは')
  } )
  .then( () => {
     timeout(1000)
  } )
  .then( () => {
    console.log('こんばんは')
  } );

一見すると、1秒ごとに値が出力されるように見えますが、 実際に動かすとsetTimeoutがうまいこと動きません。

式と文について

その前にJavaScriptは式と文で構成されています。

式というのは、簡単にいうと代入できることを指しています。 たとえば演算子や関数は変数に代入できるので式となります

var num = 1 + 1; 

var fun = function() {
    return 1
}

式は評価すると結果の値を得ることができます。 この結果の値を評価値と呼びます。

var num = 1 + 1; 
//ここでは2という評価値が表示される。
console.log(fun())

文は一つの文で処理が実行されることを指しています。 例えばif文とかfor文が該当します。

if文やfor文は式ではないので変数に代入はできません。

//  シンタックスエラーがでる
var fun = if(true){}

ブロック文

ブロック文は複数の文を書くときに使います

if(true){
   //複数の文を書いたりする
   var num = 1 + 1; 
   var fun = function() {
    return 1
   }
}

アロー関数の{}

さて本題に戻って、最初にでてきた処理はブロック文で囲ってますが 結論から言うとsetTimeoutが動いてないのはreturnがされてないのが原因です。 ブロック本体を持つ矢印関数は、自動的に値を返さないので以下のようにすることで動きます。

const timeout = (time) => new Promise((resolve) => setTimeout(resolve, time));
Promise.resolve()
  .then( () =>
    timeout(1000)
  )
  .then( () =>
    console.log('おはよう')
  )
  .then( () =>
    timeout(1000)
  )
  .then( () =>
    console.log('こんにちは')
  )
  .then( () =>
    timeout(1000)
  )
  .then( () =>
    console.log('こんばんは')
  );

つまり以下の2つは同じです 文が一つしかない場合は括弧は省略できます。

  .then( () =>
    timeout(1000)
  )

  .then( () => {
    return timeout(1000)
  )}

ブロック文とオブジェクト

アロー関数でオブジェクトを作成するときは ブロック文を括弧で囲む必要があります。

//undefined
var fun = () => {}
console.log(fun())

//OK
var fun = () => ({})
console.log(fun())

ES6の仕様で矢印の次に{があると暗黙的にブロック文が開始されてると解釈してしまうので オブジェクトを作成するときは括弧で囲まなければなりません。

細かいですが、注意しないと思うように動かないので注意しましょう。

setTimeoutとclearTimeoutの遅延束縛

setTimeoutとclearTimeoutを使うときの注意点 遅延束縛の対象が違うと挙動がだいぶ変わる。

addEventListener('click',function(e){
    var timer = null
    clearTimeout(timer)
    timer = count()
})

var count = function(){
    return setTimeout(hello, 500)
}
 

var hello = function(){
  console.log("こんにちは");
}

timerという変数をaddEventListenerの内で宣言すると、 setTimeoutで返却されるタイマーIDではなく初期値がclearTimeoutに渡されるのでタイムアウトが解除されない。 そのためこの場合すべてのclickイベントに対して処理が走る。

なのでaddEventListenerの外で変数を宣言することによって setTimeoutで返却される値がclearTimeoutに渡され、 500ミリ秒内に発生したイベントの最後のイベントに対して処理を実行することができる。

var timer = null
addEventListener('click',function(e){
    clearTimeout(timer)
    timer = count()
})

var count = function(){
    return setTimeout(hello, 500)
}
 

var hello = function(){
  console.log("こんにちは");
}

これだけは覚えていたほうがいいLinuxの権限

基本的なことですが、Linuxで権限をなんの考えもなしに変えたりする人がいますが、 ハッキングの原因にもなるので、これだけは覚えていたほうがいいのを挙げてみる。

ファイルは644が基本

アプリケーションを動かすときに基本的にファイルは他のスクリプトから呼び出されて実行されることがほとんどなので、 実行権限は与えず読み込みの権限だけ与える。

一番最初に実行されるスクリプトに関しては唯一実行権限を与える必要がありますが 言語によって多少異なる部分があり、PHPなどの言語はブラウザから実行したときにapacheやNginx、FastCGがうまいことやってくれるので 実行権限を与える必要はない

例としてapacheだとPHPapacheのモジュールとして動作をするので apacheのユーザー権限を振り分けることによって動きます

perlbash など ファイルの1行目に "#!/bin/perl" みたいなメタ情報を埋め込まないと動かない系のスクリプト言語は実行権限を付与しないと動かない。

ディレクトリは755が基本

ディレクトリはそのディレクトリ配下でファイルを作成する必要があるので実行権限を与える必要があります

その他

あとサーバーによって異なりますがパーミッションの初期状態が 775(ディレクトリ) / 664 (ファイル) の場合があります。 これは同一のグループの属する複数のユーザーがファイルを編集したりする事を想定してのものなので この場合はそういうものだと受け入れ、特に支障がない限りは手を加えないほうがいいです。

なぜKubernetesなのか。

最近になってKubernetesを使う企業が増えてきた。 それはモノリシックなサービスから複数の小さなサービスを組み合わせそれを一つのマイクロサービスとして運用するにあたって kubernetesを使うことにより、開発の質やスピードを保ったまま開発に費やすことができるからであろう。

改めてなぜKubernetesを使うのかをまとめてみた。

マイクロサービスでのkubernetes

そもそもKubernetesはコンテナ化されたアプリケーションのデプロイとスケーリングに伴う手動プロセスを省略できる。 コンテナにまたがっているアプリケーションを動かすにはコンテナを複数のホストにデプロイしなければならない。 そこでKubernetesを使うことにより、複数のホストで稼働しているコンテナをスケジューリングを行い管理することができる。 マイクロサービスを構成している各サービスはコンテナで管理し、それらのコンテナをkubernetesオーケストレーションする。 Kubernetesは自動でスケーリングを行い、障害が起きても状態を定義していれば常にその状態に維持するように稼働する。 デプロイする際も各サービスは独立して動いているので、迅速にデプロイができる。

マイクロサービスを行っている企業はすでに巨大なモノリシックな構成になっており、この構成だと途中で開発に参画したときに 巨大なサービスを把握するのに時間がかかりスムーズに開発に取り組めない、デプロイする際や障害が起きたときに影響範囲が大きくなってしまう。 マイクロサービスだと各サービスを独立させて稼働しているので、境界線が分けられ影響範囲が及ばない点からマイクロサービス化するのが主な理由
マイクロサービスを構成している各サービスはコンテナー上で動かしていて、Kubernetes を使用すると、コンテナー化したマイクロサービスのデプロイを自動化することができるので、アプリケーションのコンポーネントとマイクロサービスのすべてを簡単に管理できるようになる。 よってマイクロサービスを行っていく上でそういったことを解決するKubernetesを使用するのは大きな利点であると言えよう。

マイクロサービス以外でのKubernetes

しかし、まだ巨大なサービスとはいえないスタートアップやベンチャー企業kubernetesを使う理由があるのかという疑問が出てくる。 個人的にはあると思う。ただメリットとデメリットを考慮した上で判断すべきだと思う(これは今回に限らずだと思う)

マイクロサービスでkubernetesを使うメリットでも挙げたとおり、スケーリングと定義した状態を維持してくれるのが大きな利点であり 稼働した状態でスケーリングをしてくれるのはすごく便利だと思うし、なにか障害が起きたときに自動で修復してくれるのも注目すべきところだろう。 スタートアップやベンチャーは日々プロダクトを改善していく上でそういったインフラなどのことを気にせず、開発に注力できるのはありがたい。


気をつける点

だからといって、便利だからKubernetesを安易に導入するのはどうかなとは思う。 障害が起きたときのことを踏まえて構成を考慮しなければならないし、リソースやネットワークなども含め本番に必要な設定を理解する必要がある。 チームにKubernetesが詳しい人がいれば心強いがそうでない場合は最初は手探りな状態で運用していく形になっていくので、マイクロサービス化をすることによって開発の質を向上させたい、 頻繁なリリースがあるためサーバーやインフラ周りに時間を取られることなく開発に注力したいなどの状況であればメリットは感じられるが、 十分にKubernetesの設定を理解した上で運用していくほうがいいかなと思う。 逆にそれ以外に使う場合は手探りをしてでもKubernetesを使うメリットはそんななさそうな感じもする。


まとめ

いずれにせよ以前であれば障害が起きるたびに対応して他の業務に支障が出たり、サービスが巨大化になるにつれて影響範囲が大きくなり、開発のスピードを落としてしまうなどの 悩みの種が多かった開発やインフラ業務を便利にしたプラットフォームであり、使いこなせれば十分な恩恵を得られるであろう感じている。

GKEからCloud SQLに接続

GKEからCloud SQLに接続するときにプライベートIPで接続する方法とCloud SQL Proxyでつなぐ方法がある。

今回はCloud SQL Proxyで接続する方法をまとめてみた。

仕組みとしてはGKE(アプリケーション)からCloud SQLにつなぐときに GKEにアプリケーション(PHPやらGoなど)とNginxなどのWebサーバーだけでなくCloud SQL Proxyのコンテナも登録する必要がある

pod内にそれら3つのコンテナが動いている状態でアプリケーションは同じpod内にあるCloud SQL ProxyのコンテナのIPを指定し(127.0.0.1を指定) ポートを指定する(例えばpostgres使ってるなら5432を指定)。

図で表すとこんな感じ(公式から引用)

f:id:kmntmht116:20190530085200p:plain

そしてyamlファイルは以下のような構成( Cloud SQLの部分)

          env:
            - name: geo_api
              value: 127.0.0.1:5432
            # [START cloudsql_secrets]
            - name: geo_api
              valueFrom:
                secretKeyRef:
                  name: cloudsql-password
                  key: username
            - name: geo_api
              valueFrom:
                secretKeyRef:
                  name: cloudsql-password
                  key: password
            # [END cloudsql_secrets]
        # [START proxy_container]
        - name: cloudsql-proxy
          image: gcr.io/cloudsql-docker/gce-proxy:1.1
          command: ["/cloud_sql_proxy",
              "-instances=[INSTANCE_CONNECTION_NAME]=tcp:5432
              "-credential_file=/secrets/cloudsql/credentials.json"]
          # [START cloudsql_security_context]
          securityContext:
            runAsUser: 2  # non-root user
            allowPrivilegeEscalation: false
          # [END cloudsql_security_context]
          volumeMounts:
            - name: cloudsql-oauth-credentials
              mountPath: /secrets/cloudsql
              readOnly: true
        # [END proxy_container]
      # [START volumes]
      volumes:
        - name: cloudsql-oauth-credentials
          secret:
            secretName: cloudsql-oauth-credentials
      # [END volumes]

このyamlでproxyからCloud SQLに接続しているのが以下の部分。

  image: gcr.io/cloudsql-docker/gce-proxy:1.1
  command: ["/cloud_sql_proxy",
              "-instances=[INSTANCE_CONNECTION_NAME]=tcp:5432
              "-credential_file=/secrets/cloudsql/credentials.json"]

見ての通り、Cloud SQL proxyのimageを指定しビルドしたとあとにcloud_sql_proxyの実行でCloud SQLに接続をしている。

接続をするためにはまずCloud SQL Administration APIを有効にする必要がある。

そして Secretと呼ばれるパスワードやOAuthトークン、SSH 認証鍵などの機密データをクラスタに格納するオブジェクトを作成する必要がある。 今回は2つのSecretを作成する。

1つ目がサービスアカウントのSecretを作る。

kubectl create secret generic cloudsql-oauth-credentials --from-file=credentials.json=[PATH_TO_CREDENTIAL_FILE] 

[PATH_TO_CREDENTIAL_FILE] となっている部分はサービス アカウントの作成時にダウンロードしたキーの場所を指定しています。 作成したsecretをcloud_sql_proxyの-credential_fileに指定します。

ボリュームにも作成したSecretを定義します

          volumeMounts:
            - name: cloudsql-oauth-credentials
              mountPath: /secrets/cloudsql
              readOnly: true
        # [END proxy_container]
      # [START volumes]
      volumes:
        - name: cloudsql-oauth-credentials
          secret:
            secretName: cloudsql-oauth-credentials
      # [END volumes]

2つ目はDB情報が格納されているSecret

kubectl create secret generic cloudsql --from-literal=username=[PROXY_USERNAME] --from-literal=password=[PASSWORD]

これは接続先DBのユーザ名とパスワードを設定します。

作成したsecretの中身を確認したいときは

$ kubectl get secret [作成したsecret名] -o yaml

apiVersion: v1
data:
  password: MTIzNAo=
  username: YWRtaW4K
kind: Secret
metadata:
  creationTimestamp: ...
  name: credentials
  namespace: default
  resourceVersion: "2011810"
  selfLink: /api/v1/namespaces/default/secrets/credentials
  uid: ...
type: Opaque

作成後マニフェストファイルに以下のように指定していく

    - name: geo_api
              valueFrom:
                secretKeyRef:
                  name: cloudsql-password
                  key: username
    - name: geo_api
              valueFrom:
                secretKeyRef:
                  name: cloudsql-password
                  key: password

Cloud SQLの接続手順はこんなかんじ。