CircleCIで試行錯誤した話

## 紹介

ZOZOテクノロジーズでSREをやっています。
先ずは、いままでCircleCIについて書いてきた記事を紹介させてください

### 2018年：弊社のアドベントカレンダー

[「CircleCI + AWS Lambda function + Slackを使ったChatOps環境構築」](https://qiita.com/f-naoto832/items/bb48b9ceca6e7c9f0fac)

### その他

1. [「Incoming webhookを使ってCircleCIのSlackへの通知をJob別に分ける」](https://qiita.com/f-naoto832/items/851af83935e688497bf1)
1. [「CircleCIで重複したWorkflow(Job)を止める」](https://qiita.com/f-naoto832/items/ea529eadb290141cb6ec)
1. [「CircleCIで、直前に実行したJobによって次のJobで実行する処理を変える」](https://qiita.com/f-naoto832/items/fe142464afdbe35f7dbb)
1. [「CircleCIでDockerコマンドを使う」](https://qiita.com/f-naoto832/items/4168e73deaf7f18c90e8)
1. [「CircleCIで使われているコンテナの中身」](https://qiita.com/f-naoto832/items/6e9522a70ba1334f5bc5)


## 何を記事にするのか

今回は、上記の記事以外で、CircleCIを使う上で対処してきた内容について書きます
CIといえど、使い方は人それぞれなのですが、どなたかの助けになれればいいなと思っています


#### [ 項目 ]

1. パッケージ管理ソフト(aptとかyum)でインストールしたバイナリファイルはキャッシュに保存できても、リストアできない
1. CircleCIで使っているグローバルIPアドレスの範囲がわからない
1. `.`から始まるディレクトリ内のファイルを編集できない
1. CircleCIから外部にsshしたい
1. 外部からCircleCIのコンテナにsshしたい
1. pythonのライブラリ依存のエラーが突然起きる
1. machine executor だと pythonのバージョンがデフォルトで2系になっている


## パッケージ管理ソフト(aptとかyum)でインストールしたバイナリファイルはキャッシュに保存できても、リストアできない

例えば以下のようなconfig.yamlがあったとします

```yaml:config.yml

version: 2
jobs:
  test_job:
    machine: true
    steps:
    - run:
        name: Install redis-cli
        command: |
          sudo apt update && sudo apt install -y redis-server
    - save_cache:
        paths:
        - "/usr/bin/redis-cli"
        key: {{ checksum ".circleci/config.yml" }}

  test_job2:
    machine: true
    steps:
      - restore_cache:
          name: Restore cache
          key: {{ checksum ".circleci/config.yml" }}

```

上記の `test_job` は成功します、ただし `test_job2` はキャッシュをリストアする際に `/usr/bin`  への書き込み権限がなく失敗します

エラーは以下のようになります

```sh:shell
Downloading cache archive...
Validating cache...

Unarchiving cache...
tar: usr/bin/redis-cli: Cannot open: Permission denied
tar: Exiting with failure status due to previous errors

Error untarring cache: exit status 2
```

`/bin` 以下にあるバイナリファイルをキャッシュするのは諦めましょう
Job毎にインストールするのをおすすめします


## CircleCIで使っているグローバルIPアドレスの範囲がわからない

参考URL: https://support.circleci.com/hc/en-us/articles/115014372807-IP-Address-ranges-for-whitelisting-

```
CircleCI builds are currently run mainly from AWS East and West. 
Google Cloud Platform and additional services may be used in the future. 
For this reason we can't give a list of IP addresses that our cloud system will use.
```

要約すると「AWSとGCPとその他のサービスを使っている、もしくは使うつもりだからIPアドレスのリストは教えられないよ」と言っています

仕方ないので、CIを実行するたびにグローバルIPを調べるようにしました

参考URL: http://mgi.hatenablog.com/entry/2014/10/30/085403

以下のコマンドを使えばグローバルIPアドレスを取ってこれます

```sh:shell
$ curl -f -s ifconfig.me
```

ちなみに、ifconfig.me がサービスダウンすると、CIが失敗するので、以下のほうがいいかもしれません

```sh:shell
$ curl checkip.amazonaws.com
```


## `.`から始まるディレクトリ内のファイルを編集できない

たまに、CIの中で `.dir/sample.txt` などのファイルを書き換えようとすると、エラーは発生しないが、ファイルが書き換えられていないときがあります

そういうときは `ファイル書き換え → ファイルの置き換え` とやったほうが確実です

```yaml:config.yml
- run:
    command: |
      echo 'OK' > sample.txt
      cd sample.txt .dir/sample.txt
```

ちなみに、machineを使っていると、デフォルトのShellは `dash` になっています
これの影響なのかな？？

```sh:shell
$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Feb 19  2014 /bin/sh -> dash

# bashもちゃんとあります
$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1021112 May 16  2017 /bin/bash
```


## CircleCIから外部にsshしたい

CircleCIの公式のドキュメントだと以下のようになっています

参考URL: https://circleci.com/docs/2.0/configuration-reference/#add_ssh_keys

```yaml:config.yml

steps:
  - add_ssh_keys:
      fingerprints:
        - "b7:35:a6:4e:9b:0d:6d:d4:78:1e:9a:97:2a:66:6b:be"
```

ちなみに。上記のfingerprintを書いても失敗した気がします
やっていることは `.ssh/` にfingerprint名で暗号鍵を置いているだけです

```sh:shell
# イメージ

$ ls ~/.ssh/id_rsa_*
/home/circleci/.ssh/id_rsa_b735a64e9b0d6dd4781e9a972a666bbe

# `id_rsa_` 以降がfingerprintsから `:` を抜いた文字列になっている
```

以下のようにすればsshできます。sshする際に鍵を指定しなくてもいいみたいです。

```yaml:config.yml

steps:
  - add_ssh_keys
  - run: ssh < domain name or IP Address >
```

## 外部からCircleCIのインスタンスにsshしたい

CircleCIのWebサイトにアクセスし、実行済みのJobにアクセス、
右上のプルダウンを押し `Rerun job with SSH` でJobを起動します。

![スクリーンショット 2018-12-06 20.36.57.png](https://qiita-image-store.s3.amazonaws.com/0/259386/7fdebb6b-b2cf-0414-8e58-fb4056b5e641.png)

ターミナル (shell) で `$ ssh -p XXXXXXXX:XXXX` を実行

![スクリーンショット 2018-12-06 20.38.56.png](https://qiita-image-store.s3.amazonaws.com/0/259386/67251972-c630-f29a-07ca-ab807ceef468.png)

ちなみに、公開鍵はCircleCIと連携させているGitHub/BitBucketアカウントに登録されているものを使っているみたいです
なので、sshしたときに聞かれるパスワードはGitHub/Bitbucketからgit cloneをするときに聞かれるパスワードと同じです

プルダウンが出てこないときは、連携しているサービスに公開鍵が登録されていない可能性があります


## pythonのライブラリ依存のエラーが突然起きる

これが一番やっかいでした・・・

例えば、以下のような場合

```yaml:config.yml

    - run:
        name: Install python module and awsebcli
        command: |
          sudo apt update && sudo apt install -y python-dev python-pip
          pip install awsebcli --upgrade --user
```

pip install するとライブラリの依存でインストールできないというエラーがでることがあります
Jobの実行に使っているのがコンテナなら、Dockerhubでコンテナのタグを探し、以下のように設定すればOKです

```yaml:config.yml

    docker:
      - image: ubuntu:14.04
```

タグが `latest` でなければ、中身のライブラリのバージョンが変わることはないと思います

ちなみに、machineもバージョン指定が可能です ( pythonのバージョン管理には何度泣かされたか・・・ )

```yaml:config.yml

jobs:
  build:
    machine:
      image: circleci/classic:2017-01
```

ちなみに以下のように、ライブラリのバージョンを固定してもOKです

```sh:shell

pip install awsebcli==3.14.5 --upgrade --user
```

## machine executor だと pythonのバージョンがデフォルトで2系になっている

machine executor は shell scriptを使ってライブラリをインストール管理しているらしいです

参考URL: https://circleci.com/docs/2.0/vm-service/#customization

ちなみにGitHubではこんな感じで `Python 2.7.11` がデフォルトバージョンになっています。

```Dockerfile:Dockerfile
ADD circleci-provision-scripts/python.sh /opt/circleci-provision-scripts/python.sh
RUN circleci-install python 2.7.10
RUN circleci-install python 2.7.11
RUN circleci-install python 3.4.3
RUN circleci-install python 3.5.1
RUN sudo -H -i -u ubuntu pyenv global 2.7.11
```

最終的に pyenv global 2.7.11 となっているので、 3系を使いたい場合は以下のようにします

```yaml:config.yml
- run:
    command: |
      pyenv global 3.5.1
```

## 感想

いろいろと苦労を書きましたが、結果的にCircleCIはGitHubとの連携や各種APIが優れているので、すごく使いやすいです。
個人的には、GitHubをお使いであれば非常におすすめです！
ここまで、読んでいただきありがとうございました
また、CircleCIのアドベントカレンダーに参加できたことを光栄に思います