CircleCI で CIRCLE_SHA1 を使う場合の注意
過去に自分がミスった内容と似た事例を他にも見かけたので、今更ながらメモ。
TL; DR
- CircleCI の ECS/ECR へのデプロイに関するドキュメントで、コンテナイメージのタグに
CIRCLE_SHA1
の値が設定されているが、これには要注意 - CircleCI における
CIRCLE_SHA1
の値は、最終コミットを識別するハッシュ値だよ - Git上のブランチやタグに依って変わるわけではないので、ブランチ・タグによって異なるものの識別には使えないよ
- イメージタグのような識別子を作成する場合、それが必要なユニーク性を持っているか確認することが必要だよ
CircleCI で ECR/ECS にデプロイするときのコンテナイメージタグ
CircleCI 公式のドキュメントとして、以下の記事が公開されています。 (2019/12/15現在)
上記記事より引用
version: 2.1 orbs: aws-ecr: circleci/aws-ecr@0.0.2 aws-ecs: circleci/aws-ecs@0.0.3 workflows: build-and-deploy: jobs: - aws-ecr/build_and_push_image: account-url: "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com" repo: "${AWS_RESOURCE_NAME_PREFIX}" region: ${AWS_DEFAULT_REGION} tag: "${CIRCLE_SHA1}" - ...
ビルドの結果出来上がる個々の コンテナイメージを識別するものとしてタグを設定します(上記例の tag
)が、ここでは、タグに設定する値は "${CIRCLE_SHA1}"
となっています。
(以前のドキュメントでは、普通に docker
コマンドを使用して docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com:${CIRCLE_SHA1}
という感じになっていたはず…)
CIRCLE_SHA1 って?
さて、この CIRCLE_SHA1
に設定される値はどのようなものでしょうか。こちらも CircleCI の公式ドキュメントで説明されています。
VCS が Git であれば、 Git のコミットハッシュと同一の値になります。CircleCI でビルドを開始する際に Git リポジトリから取得した内容 (バージョン断面) を識別するものと考えていいと思います。
コンテナイメージタグに CIRCLE_SHA1 を設定すると
この CIRCLE_SHA1
をコンテナイメージタグに使うと、どうなるでしょう?
「Git リポジトリの内容が同一ならば、ビルドされるコンテナイメージも同一である」という条件が成り立つのであれば、これで問題ありません。CIRCLE_SHA1
がリポジトリの内容ごとにユニークであるので、そこと 1:1 に対応付けられるコンテナイメージも CIRCLE_SHA1
で識別することができます。
しかし、上記の条件が必ずしも成り立たない場合、つまりGitリポジトリが同一であっても、できあがるコンテナイメージが異なる場合がある(異なるイメージそれぞれを管理する必要がある)場合は注意が必要です。
例えば、よく見かける方式として、本番環境やステージング環境にリリースを行うために、Git 上の master から特定のブランチにマージし、それをトリガーとして CircleCI で各環境向けのビルドおよびデプロイを行う、という方式を考えます。
コンテナイメージ内に環境の情報を含める場合は、当然、リポジトリの内容が同一であっても、本番環境向けとステージング環境向けでできあがるコンテナイメージに差異が発生します。しかし、 CIRCLE_SHA1
の値は、トリガーとなったブランチに依らず。あくまでも最終コミットのハッシュ値によって決定されるため、それぞれのコンテナイメージに同一のタグが設定されることになります。
Docker および ECR の仕様上、すでに存在するタグと同一のタグを付与して Push した場合、古い方のイメージからはタグが剥がされてしまうため、例えば ステージング環境へのデプロイ後に本番環境へのデプロイを行った場合、ステージング環境の ECS クラスタにも本番環境用のコンテナイメージがデリバリされてしまうことになります。また、並行して複数環境のビルド・デプロイを行って、完了する順番が不定となるような場合、見かけ上ランダムにコンテナイメージが切り替わるため、再現検証の難しい障害となることがあります。
回避方法
この問題をするには、以下のやり方が考えられます
インフラ構成や実装の手間、ユニーク性の要件、費用等に合わせた方法を取るとよいでしょう。
まとめ
CIRCLE_SHA1
(に限りませんが) を何かの識別子として使用する場合、そのユニーク性が識別する対象のユニーク性と合致するかどうか、毎回きちんと検証しましょうというお話でした。
必要最低限のrequirements.txtを作成する (virtualenv版)
2017年に書いた、「必要最低限のrequirements.txtを作成する」という記事に未だに多少のアクセスがありまして、今現在でこれを参考にされてしまうとちょっとアレだなあと思いましたので。
(当時は Docker 憶えたてでイキっていました。反省)
クリーンな Python 環境を得るには virtualenv がおすすめ
virtualenv は、システムの Python 環境から、任意のディレクトリ以下に最低限のライブラリや実行ファイルをコピーし、クリーンな仮想 Python 環境を構成できるツールです。
通常は、開発したいプロジェクトやプロダクトごとに仮想環境を構成し、そのプロダクトに必要なライブラリだけ仮想環境にインストールして使用する、という使い方になると思いますが、開発で試行錯誤した結果、開発環境がプロダクションに不要なライブラリで汚染されていて整理したい、なんてこともあると思います。
そんなときは慌てず、別のディレクトリに改めてクリーンな仮想環境を作って、作成したソースコードをコピーした上で、改めて必要最低限のライブラリを pip install
してテストし、問題なければ pip freeze
してあげれば良いです。
使い方
virtualenv が未インストールであれば、インストールします。 pip install virtualenv
でインストールできますが、特定のバージョンを入れたいなどオプションについてはドキュメントを参照。
仮想環境を作りたいディレクトリに移動し、環境を作成します。
$ mkdir -p ~/path/to/project $ cd ~/path/to/project $ virtualenv . Using base prefix '/home/user/.pyenv/versions/3.7.5' New python executable in /home/user/path/to/project/bin/python3.7 Also creating executable in /home/user/path/to/project/bin/python Installing setuptools, pip, wheel... done.
すると、このディレクトリの下に bin
, include
, lib
というディレクトリができます。bin
以下にある activate
というスクリプトを source すると、仮想環境を使用するモードになります。
$ source bin/activate (project) $
プロンプトに (ディレクトリ名)
が付加されていれば、そのディレクトリの仮想環境に入っている状態です。
この状態で、必要なライブラリを pip install
し、 pip freeze
すれば、明示的にインストールしたライブラリと依存ライブラリだけのリストが出力されます。
仮想環境から出たい場合は deactivate
コマンドを実行する ( activate 時に alias が設定されている ) か、シェルからログアウトしてください。
細かすぎて伝わらないJava10のvar仕様
いろいろ試したのでメモ。
初期化時に型が決まる
まあ当たり前ですが。
public class Main { public static void main(String... args) { var hoge = 1; // hoge は int型になる hoge = 2147483648; // Integer.MAX_VALUE + 1 System.out.println(hoge); } }
> javac Main.java Main.java:4: エラー: 整数2147483648が大きすぎます hoge = 2147483648; // Integer.MAX_VALUE + 1 ^ エラー1個
以下のようににすると、iはint型になるので桁溢れして無限ループになります。
for (var i = 0; i < 2147483648; i++) { ....
プリミティブ型も推論してくれる
public class Main { public static void main(String... args) { var i = 1; i.getClass().getName(); } }
>javac Main.java Main.java:4: エラー: intは間接参照できません i.getClass().getName(); ^ エラー1個
余計な Boxing/Unboxing は発生しないようで一安心。
匿名クラスのインスタンスで初期化すると匿名クラス型の変数になる
var hoge = new Object() { int a = 1; String s = "Hello!"; int func(int a, int b) { return a + b; } };
のようにすると、hoge
は Object 型ではなくここで定義された匿名クラスの型 (Main$1とか) になります。
従って、この同じメソッドの中であれば、 hoge.a
や hoge.func(1, 2);
のように匿名クラス内のメンバを Interface定義なしで 呼び出すことができます。おもしろいけど使い所あるかな?
以降思いついたら順次追加
無限Streamに終了条件を設定する (Java9版)
というのを以前書きましたが、Java9では Stream#takeWhile
というメソッドが追加されてこういうケースが簡単に書けるようになりました。
public static String getTreePathTakeWhile(Node node, String delimiter) { List<String> names = Stream.iterate(node, Node::getParent) .takeWhile(n -> n != null) .map(n -> n.getName()) .collect(Collectors.toList()); Collections.reverse(names); return delimiter + String.join(delimiter, names); }
これでSupplierを使った無限Streamがとても使いやすくなりますね。
必要最低限のrequirements.txtを作成する
シチュエーション
Pythonでつくったサーバーアプリを環境へのデプロイするために、依存モジュールのバージョンを固定したrequirements.txtを作りたいけど、開発環境のPython環境がだいぶ使い古しで、試しに導入した不要なモジュールもあるので、単純にpip freeze
しちゃうと余計なモジュールが大量に混ざってしまう…なんてときの対処法。
手順
注(2019/12/08追記):
この記事では Docker を使った方法を説明していますが、 Python だけ気にするのであれば virtualenv を使用する方法のほうがシンプルかつ早いので、そちらを使用しましょう。
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で使用するとこんなことになってしまいます。
そのうち修正されてほしいなあと思いつつ、とりあえずの回避方法。
$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; } // ここまで追記 }
するとこんな感じに。 スクロールバーが二重で表示されるのカッコ悪いんですが、とりあえず使うのには困らないので、これでしのぎながら公式のアップデートを待ちます…
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; } // ここまで追記 }