macからvagrantへrake spec実行ユーザ変更
macのユーザでSSH接続しようとするので、vagrant
ユーザに変えたかった。
documentには以下のように記載があった。
Serverspec with SSH backend logs in to target servers as a user configured in ~/.ssh/config or a current user. If you’d like to change the user, please edit the below line in spec/spec_helper.rb.
options[:user] ||= Etc.getlogin
~/.ssh/config
や ssh_config
に記載したがどうしても上の options[:user] ||= Etc.getlogin
が先に読み込まれて
現在のログインユーザーで実行されてしまった。
なので以下のように強引に書き換えて通った。
-options[:user] ||= 'vagrant' +options[:user] ||= Etc.getlogin
spec_helper.rb
をいい感じに書き換えて ssh_config
を読ませるようにしたい。
"msg": "Failed to connect to the host via ssh: Permission denied (publickey,password).\r\n"の対応
環境
ansible -i inventory/inventory.ini playbook -m ping
pingで疎通確認をしたところ以下のメッセージが出た。 SSH接続失敗しているようだった。
"msg": "Failed to connect to the host via ssh: Permission denied (publickey,password).\r\n"
公開鍵の権限など確認したけど問題なく原因がわからなかったのですが、
- vvv
オプションで確認したところ以下のようにSSH接続するuserが指定されていないのが原因だった。
ESTABLISH SSH CONNECTION FOR USER: None
なので、 以下のようにインベントリにユーザを指定すれば解決しました。
192.168.33.27 ansible_user=vagrant
SSHが通らない時に原因はいくつかあると思いますが、 -vvv
や -vvvv
を指定すれば原因が掴めそうです。
ReactでHello World!
Node.jsのインストール
Homebrewでインストール
brew -v brew update brew install nodejs node -v
インストール用プロジェクトの作成
JSのプログラムをプロジェクト単位で管理。
npm init -y
でパッケージの管理ファイル(package.json
)を作成する。
mkdir hello_react cd hello_react npm init -y
package.jsonの変更
{ "name": "hello_react", "version": "1.0.0", "description": "Hello React", #1 "private": true, #2 "main": "index.js", "scripts": { "start": "webpack-dev-server", #3 "webpack": "webpack -d" #4 }, "keywords": [], "author": "", "license": "ISC" }
npmパッケージをインストール
npm install react react-dom npm install webpack webpack-cli webpack-dev-server --save-dev npm install babel-cli babel-loader babel-preset-env babel-preset-react --save-dev npm install eslint eslint-loader eslint-plugin-react --save-dev npm install css-loader style-loader --save-dev
インストール結果の確認用Reackコード
- ディレクトリー作成
mkdir src mkdir public
- .babelrc作成
vim .babelrc cat .babelrc { "presets": ["env", "react"] }
- .eslintrc.json作成
vim .eslintrc.json cat .eslintrc.json { "env": { "browser": true, "es6": true }, "parserOptions": { "sourceType": "module", "ecmaFeatures": { "experimentalObjectRestSpread": true, "jsx": true } }, "extends": ["eslint:recommended", "plugin:react/recommended"], "plugins": ["react"], "rules": { "no-console": "off" } }
- webpack.config.js作成
vim webpack.config.js cat webpack.config.js module.exports = { entry: { app: "./src/index.js" }, output: { path: __dirname + '/public/js', filename: "[name].js" }, devServer: { contentBase: __dirname + '/public', port: 8080, publicPath: '/js/' }, devtool: "eval-source-map", mode: 'development', module: { rules: [{ test: /\.js$/, enforce: "pre", exclude: /node_modules/, loader: "eslint-loader" }, { test: /\.css$/, loader: ["style-loader","css-loader"] }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }] } };
- public/index.html作成
vim public/index.html cat public/index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta http-equiv="X-UA-Compatible" content="IE=Edge, chrome=1" /> <title>React App</title> </head> <body> <div id="root"></div> <script type="text/javascript" src="js/app.js" charset="utf-8"></script> </body> </html>
- src/index.js
vim src/index.js cat src/index.js import React from 'react' import ReactDOM from 'react-dom' ReactDOM.render( <h1>Hello, world!!</h1>, document.getElementById('root') )
- 確認
npm start
ターミナルに webpack: Compiled successfully.
が表示されたら ブラウザーで
http://localhost:8080
をアクセスしてHello World!
と表示されたらOK.
Pythonのクラスの基本
クラスの基本
クラスの書式
class クラス名: メソッドや属性
メソッド
クラス内の関数をメソッドと呼ぶ。 メソッドにはクラスに関連する処理を記述。
属性
クラス内のデータを属性と呼ぶ。
属性はクラスのインスタンスごとのデータを持つ。
self.hoge
のようなクラスに関するデータのこと。
classのインスタンスの利用
クラスを設計図として、インスタンスを作成
c1 = NewClass() c2 = NewClass()
右辺にクラス名()と書いてインスタンスを作成。 c1とc2がそれぞれインスタンスとなる。 作成時に値を渡したい場合は()の中に引数を渡せば良い。
initメソッド
__init__
メソッドはクラスからインスタンスが作られた直後に実行される特殊メソッド。
メソッドの書式は関数と同じ。
メソッドの第一引数にはインスタンス自身が渡される。(self)
class User(object): def __init__(self, name, age): self.name = name self.age = age def info(self): return self.name + ':' + str(self.age)
Userクラスのインスタンスを作る例。
user1 = User('田村ゆかり', 17)
インスタンス作成時にクラスタ名に渡される引数は__init__
メソッドに渡されている。
クラスのメソッドの第一引数に指定されているselfは実際に呼び出し元から引数を設定する場合は無視する。
Userクラスの場合は、インスタンス作成時にnameとageに渡される値を設定する。
吉祥寺.pm13に参加してきました
こちらの勉強会で発表してきました。
吉祥寺.pmだけど今回は西新宿。
テーマが「新しい挑戦、新しい視点」だったので、新年度に向けた決意的な感じで発表してきました。 2018年の抱負は「ちゃんと調べて、ちゃんと理解する」で行こうと思います。 最近プログラミングの勉強(Python)を始めたので、 PythonでWEBアーキテクチャの実装をして、理解を深めました。
イベント駆動や、DBもやっていきたいので頑張ります。 自分の発表内容は以下のオマージュ(パクリ)なのですが process-bookの著者様がいらっしゃり、 お世話になりました。というお気持ちで一杯になりました。 発表も盛り上がってて凄かった。
イベント駆動はこれを参考にがんばりたい
黒曜石を使って、あまり画面をみないで前を見て発表するを意識したけどまだだめ。 もっとアウトプットして、発表練習や発表の場を持とうと思います。 他の人のプログラミングの発表は何もわからん状態になってしまったので、 プログラミングの勉強がんばりたい。。。
吉祥寺.pm様ありがとうございました。
Python Programming for Web Architectures
タイトルをかっこよくしてみた。 最近PyQなるものを初めてPythonの書き方を覚えているのですが、 様々なライブラリがあることを知り、簡単なTCPサーバが書けることを知りました。
普段はWebサービスのサーバ管理をしていますが、 TCP/IPの通信をクライアントーサーバ間でどのような処理でされているのか、 IPやPortってどんな役割を果たしているのかなど実装を通じて学んでみました。
他にもApacheなどのWebサーバはpreforkモデルですが、 preforkってどんな処理なのか、Nginxが解決するC10K問題って何かなども Webサーバアーキテクチャのおさらいもしようと思います。
主に以下をパクリもとい参考にしています。 なので本記事を読まなくてもいいので、以下の記事や本だけでも読んでみて下さい。 2015年Webサーバアーキテクチャ序論 process-book Learning Python Network Programming
イベント駆動モデルはわからんので断念してます。
IP,Portそしてsocket
socketはエンティティがプロセス間通信を実行できる仮想エンドポイントで、 仮想エンドポイントを識別するために必要になるのが、IPとPort番号になる。 IPアドレスでホストがわかるけど、TCP/UDP通信でどのプロセスと通信をするのか決めるために Port番号が必要になる。
ss
,netstat
,lsof
コマンドで、何気なくどのポートがなんのプロセスか、
どのプロセスがファイルを掴んでいるのかなど確認していましたが、
socket通信を理解すると、コマンドの確認結果の理解に繋がります。
3WAYハンドシェイク
TCPとUDP通信ですが、ここではTCP通信について取り上げます。 クライアントとサーバーの間の3WAYハンドシェイクプロセスによってTCP接続を確立します。 SYN→SYN/ACK→ACKのやつですね。
TCPコネクションの図やTCP jokeなどをみると理解が深まります。
TCPサーバのリスニング接続を設定Pythonの関数create_listen_socket()として書いてみます。
def create_listen_socket(host, port): """ サーバーが接続要求を受け取るソケットを設定する """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # アドレスファミリー、ソケットタイプ、プロトコル番号を指定して新しいソケットを作成 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((host, port)) sock.listen(100) return sock
クライアントとサーバ間のメッセージのやりとりの例(ユニキャスト通信)
メッセージをsocketから受信するための関数をrecv_msg()関数として以下に定義しました。
def recv_msg(sock): """ データがソケットに到着するのを待ってから、メッセージの区切り文字として '¥0'を使用してメッセージに解析する """ data = bytearray() # 新しいバイト配列を返す。 msg = '' # ソケットから4096バイトを繰り返し読み込み、デリミタが表示されるまでバイトをデータに格納する while not msg: recvd = sock.recv(4096) # ソケットからデータを受信し、結果を bytes オブジェクトで返します。一度に受信するデータは、4096bufsize if not recvd: # ソケットが途中で閉じられたら raise ConnectionError() data = data + recvd if b'\0' in recvd: # b '\ 0'をメッセージの区切り文字にする msg = data.rstrip(b'\0') msg = msg.decode('utf-8') #バイト型から文字列型へデコード return msg
メッセージの受信を待っている間にプログラムが必要とするものは何もないので、この関数はメッセージ全体を受信するまでループ内でsocket.recv()を呼び出します。 nullバイトを受け取ったかどうかを見るために繰り返しデータをチェックし、うけとったら、null バイトを取り除き、UTF-8からデコードして受信データを返します。
最後にsend_msg()関数とprep_msg())関数を作成しました。 これはメッセージにnullバイトの区切り文字をつけるのと、UTF-8エンコーディングして送信するための関数です。
def prep_msg(msg): """ メッセージとして送信する文字列を準備する """ msg += '\0' return msg.encode('utf-8') # 文字列をバイト型へエンコード def send_msg(sock, msg): """ 文字列をソケットに送信する準備 """ data = prep_msg(msg) sock.sendall(data)
さきほど書いた関数をモジュールとして、 新たにメッセージを受けるサーバのスニペットを以下のように定義して書きました。
def handle_client(sock, addr): """ sockを通じてclientからデータを受けとり,echoを返す """ try: msg = chatmodule.recv_msg(sock) # messageを完全に受信するまでblockする print('{}: {}'.format(addr, msg)) chatmodule.send_msg(sock, msg) # 送信するまでblock except (ConnectionError, BrokenPipeError): # ConnectionError のサブクラスで、もう一方の端が閉じられたパイプに書き込こもうとするか、書き込みのためにシャットダウンされたソケットに書き込こもうとした場合に発生。 print('Socket error') finally: print('Closed connection to {}'.format(addr)) sock.close() if __name__ == '__main__': listen_sock = chatmodule.create_listen_socket(HOST, PORT) addr = listen_sock.getsockname() # ソケット自身のアドレスを返します。この関数は、IPv4/v6ソケットのポート番号を調べる場合などに使用。 print('Listening on {}'.format(addr)) while True: client_sock, addr = listen_sock.accept() print('Connection from {}'.format(addr)) handle_client(client_sock, addr)
最初に、listen_sockをcreate_listen_socket()呼び出して定義します。 次に、クライアントからの接続要求を永久にlistenし、listen_sock.accept()をブロックするmainループに入ります。 クライアント接続が開始されると、プロトコルに従ってクライアントを処理するhandle_client()関数が呼び出されます。 部分的にメインループを整理し、この一連の操作を再利用できるように別の関数にしました。 これでサーバー側の処理が書けたので、次はクライアント側です。
クライアントのスニペットも以下に書きました。
mainループでしていることはメッセージとしてq
を入力してクライアントを終了するまで永遠にループさせています。
メインループ内では、まずサーバーへの接続を作成をして、次に、ユーザーにプロンプトを表示します。
送信するメッセージを入力すると、上で書いたsend_msg()関数を使ってメッセージが送信されます。
その後、サーバーの応答を待ちます。応答がきたら、それを出力します。
クライアントとサーバがこれで書けますので、参考にしてみて下さい。
if __name__ == '__main__': while True: try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((HOST, PORT)) print('\nConnected to {}:{}'.format(HOST, PORT)) print("Type message, enter to send, 'q' to quit") msg = input() if msg == 'q': break chatmodule.send_msg(sock, msg) # 送信するまでBlock print('Sent message: {}'.format(msg)) msg = chatmodule.recv_msg(sock) # messageを完全に受信するまでBlock print('Received echo: ' + msg) except ConnectionError: print('Socket error') break finally: sock.close() print('Closed connection to server\n')
クライアントとサーバのソケット通信の動きをまとめると
- サーバはsocket、bind、listenでクライアントの接続を待ち受ける
- 接続きたらacceptにより実際のデータの読み出しまで待つ。
- データがきたら、リクエストを処理して、クライアントにレスポンスを返す。
- クライアントとの接続をcloseで閉じて、またaccept待ち状態になりライアントからの接続をLISTENする。
これでクライアントとサーバのソケット通信がsocket、bind、listenを使用して理解できたと思います。 ただ、実行するとわかりますが、ひとつのクライアントからのリクエストしか処理できません。 クライアントからの接続を accept したあとは、ループを抜けるまでは新規のクライアント接続をブロックしてしまいます。
1:1の通信なのでユニキャスト通信です。 WEBサーバアーキテクチャとしてはシリアルモデルと言われるものになります。 Apacheは複数のユーザからのリクエストにレスポンスを返せるのはなぜでしょうか、 複数のプロセスがリクエストを返しているからですね。 それでは次はマルチプロセスモデルをみていきましょう。
マルチプロセスモデル
プロセスをforkして子プロセス子プロセスにリクエスト処理を任せるモデルです。 forkはプロセスをコピーするのでリソースを使い重いと言われますが、 しかし、Copy On Write(Cow)という仕組みで、差分のみをコピーするため実際にはそこまでリソースを使いません。 ただCoWでメモリコピー負荷が抑えられていますが、リクエスト毎にforkが発生するとリソースが使われますので 事前にforkさせておくのがpreforkモデルです。 事前に一定数の子プロセスをforkして、それらを使いまわす(MaxRequestsPerChildなど)ことで、リクエスト毎にforkをしなくてすみます。
また、マルチプロセスモデルは、プロセス間通信が必要なのでforkによるパフォーマンスが低下する可能性がありますが、 メモリを共有していないということは、コード内で競合状態が発生しないのでロックなどを気にしなくてすむので、処理を書くのが簡単になります。
今回はaccept()したあとの処理をプロセスで行いました。workerモデルと呼ばれるものです。 acceptしたプロセスからworkerプロセスにsocket接続を引き渡しますので コンテキストスイッチの負荷がかかってしまいます。
while True: client_sock,addr = listen_sock.accept() proc = Process(target=handle_client, args=[client_sock, addr]) proc.start() print('Connection from {}'.format(addr)) proc.join(1)
ちなみにpreforkモデルはaccept()からclose()までの処理を各プロセスにやらせます。 accept()からclose()までの処理がシンプルに書けます。 (ただ自分は上手く書けなかったので、だれか実装例を教えてほしい)
デメリットは同時接続数がプロセスの数ということです。 同時接続数が子プロセスの数を超えると、accept()がされないので、接続は未処理となり詰まります。
マルチスレッドモデル
マルチスレッドのとマルチプロセスの違いはなんでしょうか。 基本的には同じでスレッドにはリクエストごとにスレッドを生成する1コネクション1スレッドのモデルと、 事前にスレッドをPoolしておくモデル(スレッドプール)があります。 スレッドの利点としてプロセスと比較した場合のメモリ占有量は軽量と言われています。 スレッドはリソースを共有しているため、複数のスレッド間で通信が可能で異なるメモリアドレスを読み書きすることができますが、 2つのスレッドがメモリを共有し始め、スレッドの実行順序を保証する方法がない場合は、間違った値を返したり、システム全体がクラッシュしたりする可能性があります。
if __name__ == '__main__': listen_sock = chatmodule.create_listen_socket(HOST, PORT) addr = listen_sock.getsockname() print('Listening on {}'.format(addr)) while True: client_sock,addr = listen_sock.accept() # Thread は自動的にhandle_client()関数を実行し、同時にこのwhile loopを実行 thread = threading.Thread(target=handle_client, args=[client_sock, addr], daemon=True) thread.start() print('Connection from {}'.format(addr))
接続するクライアントごとに、handle_client()関数を実行するだけの新しいスレッドを作成されます。 スレッドが受信または送信時にブロックすると、OSは他のスレッドをチェックして、 それらがブロッキング状態から抜けたかどうかを確認し、存在する場合はそのスレッドの1つに切り替えます。 スレッドコンストラクタ呼び出しのdaemon引数をTrueに設定しました。 ctrl-cでプログラムを終了できますが、明示的にすべてのスレッドを閉じる必要はありません。 複数のクライアントでこのエコーサーバーを試すと、メッセージを接続して送信する2番目のクライアントがすぐに応答を受け取るのがわかります。
キューやロックの処理はチャットサーバなどを実装するとわかると思います。 (ただ自分は上手く書けなかったので、だれか実装例を教えてほしい)
マルチスレッドのコンテキスト切り替えに伴うコスト スレッドセーフ
C10K問題
インターネットが発展してWebサーバーが同時に1万のクライアントを処理する時代になり、 マルチプロセス/スレッドではこの問題が解決できません。 解決策として、イベント駆動アーキテクチャであるNginxが誕生しました。 Nginxが目指すものはイベントループで1つのスレッドで数万の同時接続を処理することです。
しかし、全てがイベント駆動で解決するというわけではなく、マルチスレッド・アプローチで解決できたり、 他にはスケーリングの問題としてもC10Kは使われます。
また、アーバン・エアーシップ社が単一のノードで500.000件の同時接続。 C500k問題に直面した模様。 膨大な数のモバイルデバイスに通知サービスを提供するには、非常に多くのアイドル接続を並行して処理する必要があります。
今は、1000万の同時接続である「C10Mの問題」に直面しています。 このようなものは大規模分散システムの領域でしょうか。 いろんなアーキテクチャを組み合わせないと解決できなそうですね。 これらの知識についても勉強したいです。
TCP/IP - Solving the C10K with the thread per client approach C500k in Action at Urban Airship C10M
イベント駆動型サーバーアーキテクチャ
1プロセス/スレッドでは同時に複数のブロック処理を扱えないため、 新たにプロセスやスレッドを生成し処理をさせていましたね。
イベント駆動ではイベントループで一のスレッドを複数の接続にマッピングさせて 接続、リクエストの入出力操作から発生したすべてのイベントを処理させます。 新しいイベントがキューに入れられ、スレッドはいわゆるイベントループを実行します。 キューからイベントをdequeueしてイベントを処理し、次のイベントを取得するか、新しいイベントがプッシュされるのを待ちます。 したがって、スレッドによって実行される作業は、複数の接続を単一の実行フローに多重化するスケジューラの作業と非常に似ています。 次のスライドを参考になりそうです。
イベント駆動型サーバーアーキテクチャはPythonで イベント駆動型プログラミングを書かないと理解が深まりそうにないので 次回は以下を調べてみようと思います。 (だれか参考になる実装例や説明教えてください)
- イベント駆動型プログラミング
- それは何であり、どのように機能するのですか?
- asyncioモジュール
- asyncioベースのプログラミング
- Twisted
- Gevent
オブジェクト、メソッドまとめ
Socket family:socket.AF_INET=アドレス (およびプロトコル) ファミリーを示す定数で、 socket() の 最初の引数に指定することができます。 Socket type:ソケットの種類を指定します。SOCK_STREAMとSOCK_DGRAMをそれぞれ指定すると、TCPベースのソケットとUDPベースのソケットが作成されます。 socket.bind(address):ソケットを address にbindします。 socket.listen([backlog]):サーバーを有効にして、接続を受け付けるようにします。backlog が指定されている場合、少なくとも 0 以上でなければなりません (それより低い場合、0 に設定されます)。システムが新しい接続を拒否するまでに許可する未受付の接続の数を指定します。指定しない場合、デフォルトの妥当な値が選択されます。バージョン3.5 で backlogの引数が任意になりました。 socket.setsockopt(level, optname, None, optlen: int) level:SOL_SOCKET:level パラメータは、オプションのプロトコルレベルを指定します。オプションをソケットレベルで取得するには level パラメータに SOL_SOCKET を指定します。 TCP のようなそれ以外のレベルの場合、そのレベルのプロトコル番号を指定します。 フラグ:SO_REUSEADDR フラグは、 TIME_WAIT 状態にあるローカルソケットをそのタイムアウト期限が自然に切れるのを待つことなく再利用することをカーネルに伝えます。 socket.listen:サーバーを有効にして、接続を受け付けるようにします。backlog が指定されている場合、少なくとも 0 以上でなければなりません (それより低い場合、0 に設定されます)。システムが新しい接続を拒否するまでに許可する未受付の接続の数を指定します。指定しない場合、デフォルトの妥当な値が選択されます。バージョン 3.5 で変更: backlog 引数が任意になりました。 socket.sendall(bytes[, flags]):ソケットにデータを送信します。ソケットはリモートソケットに接続済みでなければなりません。オプション引数 flags の意味は、上記 recv() と同じです。 send() と異なり、このメソッドは bytes の全データを送信するか、エラーが発生するまで処理を継続します。 正常終了の場合は None を返し、エラー発生時には例外が発生します。 エラー発生時、送信されたバイト数を調べる事はできません。 バージョン 3.5 で変更: ソケットのタイムアウトは、データが正常に送信される度にリセットされなくなりました。 ソケットのタイムアウトは、すべてのデータを送る最大の合計時間となります。 システムコールが中断されシグナルハンドラが例外を送出しなかった場合、 このメソッドは InterruptedError 例外を送出する代わりにシステムコールを再試行するようになりました (論拠については PEP 475 を参照してください)。 socket.accept:接続を受け付けます。ソケットはアドレスにbind済みで、listen中である必要があります。戻り値は (conn, address) のペアで、 conn は接続を通じてデータの送受信を行うための 新しい ソケットオブジェクト、 address は接続先でソケットにbindしているアドレスを示します。新たに作成されたソケットは 継承不可 です。システムコールが中断されシグナルハンドラが例外を送出しなかった場合、このメソッドは InterruptedError 例外を送出する代わりにシステムコールを再試行するようになりました (論拠については PEP 475 を参照してください)。
Python入門者の集い #6 に参加してきました
突然ですが、煽られたのでこちらの勉強会にLTしてきました。
発表したもの
最近PyQやっているので、PyQの紹介とPythonでライブラリ使ってLinuxと仲良くなりたい話をしました。 ファイルディスクリプタのところは自分でもスライド書いてて理解を整理できたので発表するのも勉強になってよい。 黒曜石を使ったのですが良かったけど、下むいて発表してしまったので、 事前の発表練習も必要だなと反省。
その他感想
LTでPillowというライブラリを使ったモザイクアートがおもしろかった。 嫁は二次元にいると言っていて愛を感じた。 作ったものを公開するとたのしそうだし、周りからの反応もあると嬉しいだろうなあ。
懇親会で何人かに声をかけて貰えて、参考になったと言ってもらえたのは嬉しかったです。 次に発表するときは内容が濃いものにしたいので、Pythonで何か作ったものを発表したい。
発表する敷居が低いのでお勧めの勉強会です。