安心して Python2 の EOL を迎えるためにやったこと


自己紹介

SRE/QA チームで SRE をしている @yukin01 です。
半年ほど前までは iOS や Firebase/GCP を用いたアプリケーション開発を主にしていましたが、インフラ周りを強化したいと思い今はグロービスで SRE として AWS や GCP を触っています。

Python2 の EOL

ご存知の通り Python2 は2020年1月1日でサポート終了することが発表されていますが、もし Python2 にまだ依存しているプロダクトやツールがあるなら EOL までに何としてでも駆逐したいですよね。
EOL までのカウントダウンを確認できるサイト を見ると多少臨場感を味わえるかもしれません)

What will happen if I do not upgrade by January 1st, 2020?

If people find catastrophic security problems in Python 2, or in software written in Python 2, then most volunteers will not help fix them. If you need help with Python 2 software, then many volunteers will not help you, and over time fewer and fewer volunteers will be able to help you. You will lose chances to use good tools because they will only run on Python 3, and you will slow down people who depend on you and work with you.

改めて EOL を迎えるとどうなるのか確認しましたが、2020年1月1日以降は Python2 に脆弱性が見つかっても基本的には放置されるとのことなので、当然ですがそのまま使い続けるリスクはとても高いです。
もちろん各種ベンダーが個別でサポートを続けることはあると思いますが、順次打ち切っていくはずなので結局何かしらの対応は必要です。
弊社のインフラ環境でもいくつか Python2 の存在が確認できたので、脱 Python2 のために対応したことをお伝えできればと思います。

AWS Lambda

AWS Lambda は Python 2.7 ランタイムをサポートしています。
ランタイムサポートポリシーによると、今後60日以内に廃止予定のランタイムで Lambda を実行している場合はメールでお知らせしてくれるらしいのですが、今のところ(12/6現在)確認できていないので EOL 後も使用すること自体はできそうです。

弊チームでは各種 CloudWatch Events の Slack 通知やスクリプトの定期実行などで Lambda の Python/Go/Node.js ランタイムを利用していますが、Python に関してはほとんどが Python3 でした。

ただ、S3にアップロードされたファイルに対するアンチウイルスソフトとして ClamAV を Lambda 上で実行しているのですが、それだけは Python2.7 で動いています。
これは Lambda 用の zip ファイル作成に利用している bucket-antivirus-function というツールがまだ Python2.7 用のファイルしか吐き出せないことが理由なのですが、すでに Python3 移行の PR が立っているようなので少し様子を見ることにしています。

自前 Python スクリプト

サーバ上でのちょっとした作業を Python スクリプト化しておくと非常に便利ですよね。
私自身は元々 Python の経験はありませんでしたが、今は AWS CLI を呼ぶシェルスクリプトを書くのも boto3 を使って Python で書くのもそんなに変わらないので学習コストの低さを実感しています。
これらのスクリプト群はすべて shebang で Python3 が指定されていたので基本的には対応不要でした。

Ubuntu AMI

グロービスのプロダクトは基本的に Ubuntu ベースの AMI 上で動いていて、Ansible と Packer を用いてプロビジョニングしています。また Ubuntu のバージョンは 16.04 or 18.04 を使っていて、どちらもデフォルトでは Python2 はインストールされていません。
しかし、稼働中のすべてのインスタンス(AMI)には Python2 が入っていました。

$ which python2.7
/usr/bin/python2.7

いくつかの原因があったので1つずつ潰していきました。

Ansible を実行している Python

Packer で AMI を作成するときの Provisioner として ansible-local を使っているので、あらかじめ shell provisioner で Ansible を入れているのですがその方法に問題がありました。

packer.json(簡略)
"provisioners": [
  {
    "type": "shell",
    "inline": [
      "sudo apt-add-repository -y ppa:ansible/ansible",
      "sudo apt-get update",
      "sudo apt-get -y install ansible"
    ]
  }
]

apt が提供する Ansible パッケージがそもそも Python2 に依存しているので、apt-get 経由でインストールすると Python2.7 で実行されるようになってしまいます。

$ apt-cache depends ansible

ansible
  Depends: python-crypto
  Depends: python-jinja2
  Depends: python-paramiko
  Depends: python-pkg-resources
  Depends: python-yaml
  Depends: <python:any>
    python
  Depends: <python:any>
    python
  Depends: python-httplib2
  Depends: python-netaddr
  Recommends: python-selinux
  Suggests: sshpass
$ ansible --version

ansible 2.9.1
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.12 (default, Oct  8 2019, 14:14:10) [GCC 5.4.0 20160609]

対応としては、以下のページを参考に pip3 経由でインストールするように修正しました。

packer.json(簡略)
"provisioners": [
  {
    "type": "shell",
    "inline": [
      "sudo apt-get update",
      "sudo apt-get -y install python3-pip",
      "sudo pip3 install ansible"
    ]
  }
]
$ ansible --version

ansible 2.9.1
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.5/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.5.2 (default, Oct  8 2019, 13:06:37) [GCC 5.4.0 20160609]

Python のパスやバージョンが変わっているのが確認できたので OK です。

Ansible Playbook 内のタスク

数年運用されていることもあり、pip2 経由のパッケージが残っていたので確認して pip3 に移行しました。
加えて apt モジュールでインストールしているパッケージの中に Python2 依存のものがないかチェックしました。

tasks/main.yml(例)
- name: Install packages from apt
  apt:
    name:
      - python3-pip # OK
      - software-properties-common # OK
      - htop # OK
      - python-pip # NG
      - python-properties-common # NG
      - dstat # NG

- name: Install packages from pip2
  pip:
    name:
      - boto
      - awscli
    executable: pip2 # NG

各タスクを実行するときの Python

これまでの変更箇所を踏まえて Packer を走らせましたが、/usr/bin/python: not found というエラーでコケてしまうタスクがありました。
Ansible にはタスクの実行に使用する Python インタプリタを指定できるという機能がありますが、デフォルトで /usr/bin/python を探してしまっていたのが原因と考えられたので以下の変数を明示的に指定することで解決しました。

ansible_python_interpreter=/usr/bin/python3

↑によると ansible.cfg やインベントリファイル、コマンド実行時のオプションとして指定できるようです。

以上の対応で AMI から Python2 を取り除くことが出来ました

$ which python2.7

$ which python

Ubuntu 自体の Python2 サポート

余談ですが Ubuntu 自体も Python2 の依存を切り離すのに苦労しています。
Ubuntu には以下の4つのリポジトリがありますが、16.04 と 18.04 については Python2 自体が main に入っているので EOL 後も公式のリポジトリからインストール可能です。
(もちろんユーザー側が積極的に使っていいという話ではないと思いますが…)

The four main repositories are:

  1. Main - Canonical-supported free and open-source software.
  2. Universe - Community-maintained free and open-source software.
  3. Restricted - Proprietary drivers for devices.
  4. Multiverse - Software restricted by copyright or legal issues.

次期 LTS の Ubuntu 20.04(コードネーム "Focal Fossa")では本格的に Python2 が除去されるらしいのですが、実際のところどうなるのか気になりますね…!