読者です 読者をやめる 読者になる 読者になる

必要最低限のrequirements.txtを作成する

シチュエーション

Pythonでつくったサーバーアプリを環境へのデプロイするために、依存モジュールのバージョンを固定したrequirements.txtを作りたいけど、開発環境のPython環境がだいぶ使い古しで、試しに導入した不要なモジュールもあるので、単純にpip freezeしちゃうと余計なモジュールが大量に混ざってしまう…なんてときの対処法。

手順

Dockerできれいな仮想環境を作る

とりあえず、pyenvでPythonをインストールできる最低限の環境を作ります。導入するモジュールによっては追加が要るかも。

以下のようなDockerfileを作る。とりあえずUbuntu14ベースで作ってますが、他のディストロならもうちょっとシンプルになるかも。

FROM ubuntu:14.04

ARG http_proxy
ARG https_proxy

ENV http_proxy=${http_proxy:-} \
    https_proxy=${https_proxy:-}

RUN apt-get update && apt-get install -y \
    build-essential python git curl zlibc zlib1g-dev \
    libssl-dev libreadline-dev libbz2-dev libsqlite3-dev \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /root

RUN git clone https://github.com/yyuu/pyenv.git .pyenv
ENV PATH /root/.pyenv/bin:$PATH
RUN echo 'eval "$(pyenv init -)"' >> .bashrc

CMD ["/bin/bash", "-i"]

これを適当なディレクトリに置いてdocker buildします。

$ cd clean_pyenv # 上記Dockerfileがおいてあるディレクトリに移動
# 直接インターネットが見られる環境の場合
$ docker build -t clean_pyenv ./
# プロキシが必要な場合
$ docker build --build-arg http_proxy=$http_proxy --build-arg https_proxy=$https_proxy -t clean_pyenv ./

仮想環境内で、pyenvを使ってターゲットバージョンのPython環境を作る

上記で作ったDockerイメージにはpyenvがインストール済みなので、これを使って必要な環境を作っていきます。 コンテナを起動してログイン、pyenv installで目的のバージョンをインストールして切り替えます。

$ docker run -it --rm clean_pyenv
$ pyenv install 2.7.13
$ pyenv global 2.7.13

アプリから直接参照しているモジュールのリストを用意してpip install -rした後、pip freezeする

例として、アプリから直接参照している外部モジュールがboto3 1.4.4だけだとします。 (モジュールがたくさんあるようであれば、列挙したテキストファイルを別のターミナルからdocker cpで送り込んであげればよいです。モジュールバージョンが最新でよければ、バージョン番号は省略でもOK)

$ echo boto3==1.4.4 > reqirements_part.txt
$ pip install -r reqirements_part.txt
$ pip freeze > requirements.txt

これで、依存モジュールも含むrequirements.txtが生成されるはずです。

途中間違えたら、コンテナからログアウトして終了してしまえば、pyenvだけのきれいな状態からやり直すことができます。もちろんDockerイメージは再利用可能。

Gitでローカル・リモート一緒にブランチ名を変更したい

Git 2.11.1で確認。

シチュエーション

hageってブランチ作って、

$ git checkout -b hage

諸々修正して、

$ git commit -a
$ git push -u origin hage

commit・pushした後に、

「あれ、ブランチ名間違えた。hogeだった」

ということで、ローカルとリモート併せて名前変えたい。

失敗例

upstreamブランチをリモートから削除して

$ git push --delete origin hage

ローカルブランチの名前を変えて

$ git checkout hage
$ git branch -m hoge

そのまま再push!

$ git push -u origin hoge
...
 * [new branch]      hoge -> hage
                             ~~~~

あれー?

$ git push --delete origin hage
$ git branch -vv
...
hoge ff0da9d [origin/hage: gone] ....
...

リモートのブランチが消えても、一度設定されたupstreamの向き先は残ってるっぽい。

解決

upstreamブランチを削除してローカルブランチ名変えた後、明示的にupstream設定を削除する必要があるようです。

$ git branch --unset-upstream

その後、再push

$ git push -u origin hoge
...
 * [new branch]      hoge -> hoge
                             ~~~~

めでたしめでたし。

atom 1.9.x + graphviz-preview の表示バグの回避

Atom を使っていて、ちょっと図を書いて整理したいなんてときに便利な graphviz-preview ですが、2016/8/27現在、graphviz-previewの更新が、対応バージョン1.7.0で止まっており、現時点の最新である1.9.8で使用するとこんなことになってしまいます。

f:id:shout_poor:20160827195503p:plain

そのうち修正されてほしいなあと思いつつ、とりあえずの回避方法。

$HOME/.atom/packages/graphviz-preview/styles/graphviz-preview.lessテキストエディタで開き、以下のwebview要素のstyleを追記します。

// The ui-variables file is provided by base themes provided by Atom.
//
// See https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less
// for a full listing of what's available.
@import "ui-variables";

.graphviz-preview {
  background-color: #fff;
  overflow: scroll;
  box-sizing: border-box;
  padding: 0;

  iframe {
    width: 100%;
    height: 100%;
    border: 0;
  }

  // ここから追記
  webview {
    width: 100%;
    height: 100%;
    border: 0;
  }
  // ここまで追記

}

するとこんな感じに。 スクロールバーが二重で表示されるのカッコ悪いんですが、とりあえず使うのには困らないので、これでしのぎながら公式のアップデートを待ちます…

f:id:shout_poor:20160827200504p:plain

2016/08/28 追記

.graphviz-preview クラスと webview 要素に overflow: none; を設定してやると余分なスクロールバーが消えました。実際のスクロールバーは、webview内に貼られるobjectが表示してくれるようです。

// The ui-variables file is provided by base themes provided by Atom.
//
// See https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less
// for a full listing of what's available.
@import "ui-variables";

.graphviz-preview {
  background-color: #fff;
  overflow: none;      // scroll -> none
  box-sizing: border-box;
  padding: 0;

  iframe {
    width: 100%;
    height: 100%;
    border: 0;
  }

  // ここから追記
  webview {
    width: 100%;
    height: 100%;
    border: 0;
    overflow: none;
  }
  // ここまで追記

}

追記:Let's Encrypt のEV証明書更新

この記事の続きというか補足。

自サイトをHTTPS化してみた - NoisySpot/メモ帳

Let's Encryptで発行されるEV証明書の有効期限は90日間と短めに設定してありますが、Certbotのクライアントツールを使用していれば更新は簡単。コマンド一発です。

$ sudo letsencrypt renew 

確認のためのDry runも可能です。(--dry-run オプション)

ただ、このコマンドは証明書が失効する前に実行しても、何もしてくれません。つまり証明書が期限切れ状態となる期間が発生してしまいます。

Certbotの解説によれば、cronやsystemdでの1日2回の実行が推奨されていますので、失効する期間を最小限にするためにも、こちらに従って定時バッチ化しておくのがいいと思います。

自サイトをHTTPS化してみた

はてなブログでホストしていただいている等ブログの他に、勉強のために自ドメインでサイトを構築しております。 (内容はあまりありません。日記的ブログだった残骸があるだけです…)

Noisy Spot | 個人的まとめ

で、なんか最近調子が良くなかったので、OSごと再構築しまして、ついでに Let's Encrypt のDV証明書を利用してHTTPS化してみました。

letsencrypt.org

以下は手順メモです。

作成時の環境

  • Ubuntu 16
  • Nginx 1.10.0
  • OpenSSL 1.0.2g

クライアントツールの取得と申請~証明書発行

Let's Encrypt への申請、認証、証明書発行の一連の流れを自動化してくれるクライアントツールは、対象の環境や実装手段によりいくつか開発されているようです。Let's Encrypt推奨の方法としては、以下のCertbotというサイトでWebサーバとOSを選択して、表示された手順に従うというものでした。今回はそれに従いました。

certbot.eff.org

まずは apt でクライアントツールを導入します。

$ sudo apt-get update
$ sudo apt-get install letsencrypt

で、nginxを起動した状態で、letsencrypt コマンドを打ちます。

$ letsencrypt certonly --webroot -w [[Webサーバのドキュメントルートのパス]] -d [[申請するドメイン名]]

すると、CUIが立ち上がり、メールアドレスを要求してくるので入力します。申請失敗時などはここで入力したアドレスに連絡が飛んで来るようなので、使えるアドレスを入力する必要があります。

メールアドレスを入力し、利用規約などをAgreeすると、申請・認証が開始します。以下の様な流れで動作するようです。

  1. メールアドレスとドメイン名をLet's Encryptに送付して発行申請する。戻り値としてシグネチャが返される。
  2. Webサーバのドキュメントルートに .well-known/acmechallenge/ ディレクトリを作成し、返されたシグネチャを格納
  3. Let's Encryptのサーバから、http://[[申請したドメイン]]/.well-known/acmechallenge/ のリクエストを飛ばし、取得したシグネチャと発行したものが同一となればドメイン所有者認証の完了
  4. 証明書と鍵がLet's Encryptから発行され、クライアントツールはそれらを所定の場所に格納する。

以上がコマンド実行から2分くらい放置で完了。簡単!

上記コマンド完了後 /etc/letsencrypt に下記のファイルが格納されます。

.
|-- accounts
|   `-- acme-v01.api.letsencrypt.org
|       `-- directory
|           `-- [[ハッシュコード]]
|               |-- meta.json
|               |-- private_key.json
|               `-- regr.json
|-- archive
|   `-- [[ドメイン名]]
|       |-- cert1.pem
|       |-- chain1.pem
|       |-- fullchain1.pem
|       `-- privkey1.pem
|-- csr
|   `-- 0000_csr-certbot.pem
|-- keys
|   `-- 0000_key-certbot.pem
|-- live
|   `-- [[ドメイン名]]
|       |-- cert.pem -> ../../archive/[[ドメイン名]]/cert1.pem
|       |-- chain.pem -> ../../archive/[[ドメイン名]]/chain1.pem
|       |-- fullchain.pem -> ../../archive/[[ドメイン名]]/fullchain1.pem
|       `-- privkey.pem -> ../../archive/[[ドメイン名]]/privkey1.pem
`-- renewal
    `-- [[ドメイン名]].conf

Nginxから参照するのは以下の2ファイルです。(再発行時にそなえ、ファイル実体を直接参照するより、live以下のシンボリックリンクを参照するほうがよいでしょう)

  • サーバ証明書 /etc/letsencrypt/live/[[ドメイン名]]/fullchain.pem
  • 秘密鍵 /etc/letsencrypt/live/[[ドメイン名]]/privkey.pem

この時点で、ドキュメントルートの .well-known ディレクトリは削除しておきます。

ちなみに、WebサーバがApache2等の場合、letsencryptのサブコマンド(上記の certonly)を run とすると、上記の証明書発行に加え、Webサーバの設定ファイルの更新までやってくれるようなのですが、現時点ではNginxはそこまでサポートされていないようです。

なので、以下はNginxへの個別設定となります。通常のhttpsサイト構築とあまり変わらないと思いますので、その辺ご存じの方は、サーバ証明書秘密鍵の格納場所がわかれば十分でしょう。

DHパラメータファイルの作成

TLSの暗号化アルゴリズムの一つ、Diffie-Hellman暗号で使用するDHパラメータファイルを作成します。

$ sudo openssl dhparam 2048 -out /etc/nginx/dhparam.pem

`2048' は生成するビット長です。数値が大きいほど生成に時間がかかりますが、小さいと脆弱になるので、とりあえずLogjam Attack対策での推奨値である2048を設定しました。うちの環境(2CPUのVPS)で1分くらいで生成完了。 ファイルパーミッションは600にしておきます。

Nginxの設定

/etc/nginx/sites-available/ 以下に任意の名前でファイルを作成し、サイト設定を記入します。

server {
       listen 80;
       server_name www.noisyspot.jp;
       return 301 https://$server_name$request_uri;   # httpにリクエストが来たらpermanent redirectでhttpsに飛ばす
}

server {
        listen 443 ssl default_server;
        listen [::]:443 ssl default_server;

        ssl_certificate /etc/letsencrypt/live/[[ドメイン名]]/fullchain.pem;    # サーバ証明書
        ssl_certificate_key /etc/letsencrypt/live/[[ドメイン名]]/privkey.pem;  # 秘密鍵
        ssl_session_tickets on;

        ssl_dhparam /etc/nginx/dhparam.pem;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!aNULL!eNull:!EXPORT:!DES:!3DES:!MD5:!DSS';
        ssl_prefer_server_ciphers on;


(以下略)

ssl_protocolsssl_ciphers は、暗号化仕様の使用制限に関する設定です。今回は以下のサイトを参考にしました。

qiita.com

このへん、脆弱性が見つかっては新しい物が策定されが繰り返し、またWebサーバやブラウザ側のサポート状況にもよりますので、世間に注意しつつ定期的に更新していく必要があるでしょう。

あとは、/etc/nginx/sites-enabled/ から、上記で作成したサイト設定ファイルへのシンボリックリンクを張り、defaultのシンボリックリンクを削除すれば、設定は以上です。念のため nginx -t で設定ファイルのバリデーションをした後、 systemctl restart nginx サービスを再起動すればOK。

Emacsへのphp-mode, php-completion の導入方法

下記の参考資料通りにやってみたところいくつかエラーが出たので、試行錯誤して正常にインストールできた手順をメモ。

前提

必要なパッケージのインストール

M-x package-install-package でパッケージの一覧を表示し、以下のパッケージをインストールする。

  • cl-lib
  • anything
  • auto-complete
  • php-mode
  • php-completion

cl-libはphp-modeインストール時に一緒に入るが、それ以外は個別に入れること

init.elの設定

init.elに以下のスクリプトを追記。(入力補完は C-oバインドしていますが、他のキーにしたい場合は (kbd "C-o") の部分を変えてください)

(require 'php-mode)
(add-hook 'php-mode-hook
      (lambda ()
        (require 'php-completion)
        (php-completion-mode t)
        (define-key php-mode-map (kbd "C-o") 'phpcmp-complete)
        (make-local-variable 'ac-sources)
        (setq ac-sources '(
                   ac-source-words-in-same-mode-buffers
                   ac-source-php-completion
                   ac-source-filename
                   ))))

2016/07/02追記

別の環境でインストールしようと思ったら、パッケージリストにphp-completionが出てこなかったので原因調査中です… →php-completionパッケージはmelpaではなくmarmaladeにありましたので前提に追記。

参考資料

PHPを扱うようになったのでemacsにphp modeを入れてみた。 - Qiita

Java8 定義済み関数型インタフェース(主要なものメモ)

忘れるのでメモ。プリミティブ型用のインタフェースは省略。

Interface Scala風関数型表記
Consumer<T> (T) => void
BiConsumer<T, U> (T, U) => void
Supplier<T> () => T
Function<T, R> (T) => R
BiFunction<T, U, R> (T, U) => R
Predicate<T> (T) => boolean
BiPredicate<T, U> (T, U) => boolean
UnaryOperator<T> (T) => T
BinaryOperator<T> (T, T) => T
Comparator<T> (T, T) => int

Comparator<T> は、Java7の頃から存在する、ソート等での順位付けを行う比較関数を実装するための Interface のため、 java.util パッケージに属する。その他は java.util.function パッケージに属する。