IT技術で仕事を減らしたい!

ITエンジニアのメモ+α

Python Poetry pyinstallerのインストールエラー

どうも、nippa です。

poetry で pyinstaller をインストールしようとした際にエラーが出ました。

他のパッケージでも起こりうることなので、その解決方法をまとめておきます。

環境

  • macOS 11.6
  • poetry 1.1.14
  • pyinstaller 5.3

インストールのエラー内容

まずpoetry initで poetry の環境を生成しました。

poetry の初期設定後、poetry add pyinstallerを実行してインストールしようとした際に、以下のようなエラーが発生しました。

Using version ^5.3 for pyinstaller

Updating dependencies
Resolving dependencies... (0.0s)

  SolverProblemError

  The current project's Python requirement (>=3.11,<4.0) is not compatible with some of the required packages Python requirement:
    - pyinstaller requires Python <3.11,>=3.7, so it will not be satisfied for Python >=3.11,<4.0

  Because no versions of pyinstaller match >5.3,<6.0
   and pyinstaller (5.3) requires Python <3.11,>=3.7, pyinstaller is forbidden.
  So, because blog-pyinstaller depends on pyinstaller (^5.3), version solving failed.

  at ~/.poetry/lib/poetry/puzzle/solver.py:241 in _solve
      237│             packages = result.packages
      238│         except OverrideNeeded as e:
      239│             return self.solve_in_compatibility_mode(e.overrides, use_latest=use_latest)
      240│         except SolveFailure as e:
    → 241│             raise SolverProblemError(e)
      242│
      243│         results = dict(
      244│             depth_first_search(
      245│                 PackageNode(self._package, packages), aggregate_package_nodes

  • Check your dependencies Python requirement: The Python requirement can be specified via the `python` or `markers` properties

    For pyinstaller, a possible solution would be to set the `python` property to "<empty>"

    https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies,
    https://python-poetry.org/docs/dependency-specification/#using-environment-markers

原因は、pyinstaller が要求する python のバージョン <3.11,>=3.7poetry initで設定した python のバージョンが合わないことです。

pyinsaller の配布ページにもありますが、対応している python のバージョンが、python 3.10 までであるので、3.11 が対応していないです。

解決方法

pyproject.toml の python のバージョン指定を適切に修正することで解決可能です。

# 修正前
[tool.poetry.dependencies]
python = "^3.9"

# 修正後
[tool.poetry.dependencies]
python = "^3.9,<3.11"

python 3.11 には対応していないという意味の<3.11を追加して上げると、インストール可能になります。

poetry のバージョンの書き方

poetry でのバージョン指定方法は、以下のような形でかけます。

python 3.7 から 3.10 までの場合、以下のようになります。

python = ">=3.7,<3.11"

python 3.9 だけの場合、以下の通りです。

python = "^3.9"

感想

今回 pyinstaller のインストール時にエラーが出ましたが、他のパッケージ、ライブラリでも起こりうるものなので、内容を調べてまとめました。

エラーに困っている人の役にたてればと思います。

ではでは、また次回。

Docker Swarmモードのネットワーク設定

どうも、nippa です。

今回は Docker の Swarm モードでコンテナ同士の通信設定をまとめたいと思います。

コンテナ同士の通信ができると、サービスごとに設定もできるため広がりが出てきます。

環境

Virtual Box 上に Linux サーバを 2 台構築

  • ubuntu 20.04
  • メモリ:2GB
  • ストレージ: 10GB

サーバは

  • Manager サーバ(manager node + work node)
  • Worker サーバ(work node)

と定義します。()の中は swarm モードでの node 構成を示しています。

設定の詳細はこちらの記事に記載してあります。

timesaving.hatenablog.com

ネットワークドライバの種類

Docker 公式ページで記載されているネットワークについてまとめると以下の表のようになります。

ドライバ名 用途
bridge デフォルトのネットワーク。アプリケーションからスタンドアロンにアクセス可能で、外部から直接コンテナにアクセスする場合はこの設定になります。
host スタンドアロンコンテナとホスト間で直接通信する場合の設定で、外部からのアクセスは出来ません。
overlay 複数の Docker デーモン間で相互通信可能な設定になります。Swarm モードで利サービス同士の通信を刷る場合はこの設定になります。
ipvlan IPv4IPv6 を直接コンテナに割り振り、制御する設定になります。
macvlan コンテナに物理的な Mac アドレスをを仮想的に割り振る設定になります。
none ネットワーク機能をすべて無効な設定です。

この他に、サードパーティプラグインをインストールすれば利用可能になります。

Docker のネットワークの生成

コンテナ間の相互通信を行うためには、overlayドライバを利用します。

docker のネットワークの生成は下記のコマンドで行います。

docker network create -d [ドライバ名] [ネットワーク名]

Swarm モードのサービス間での相互通信

まず、overlayドライバでのネットワークを生成します。

Swarm モードのサービス間の通信の場合は、以下のオプションで作成します。

docker network create -d overlay [ネットワーク名]

スタンドアロンコンテナをネットワークに接続する場合には--attachableオプションをつけて、ネットワークを生成します。

docker network create -d overlay --attachable [ネットワーク名]

今回は IP アドレスの範囲指定や、サブネット、ゲートウェイについては指定しません。

必要な場合はdocker network create --helpでオプションを確認できますので、用途に合わせて設定してください。

オーバーレイネットワーク上のトラフィックの暗号化

トラフィックの暗号化が必要な場合は、--opt encryptedオプションを利用することで暗号化可能です。

12 時間ごとに鍵が変更される仕様になっています。

ただし、Windows 上ではサポートされていない機能ですので、ご注意ください。

docker network create -d overlay --opt encrypted --attachable [ネットワーク名]

Swarm モードのサービスごとの設定

Swarm モードのサービスごとに公開ポートの設定が必要になります。

公開ポートが 8080 で、内部のサービスポートが 80 で、通信方式が TCP の場合、次のコマンドでポートの設定が可能です。

# サービスの生成
docker service create -p 8080:80/tcp [サービス名]

# サービスの更新
docker service update -p 8080:80/tcp [サービス名]

この書き方は複数ありますので、Docker 公式でご確認ください。

感想

今回ネットワークの設定方法をまとめましたので、実際に設定してコンテナ間の通信をしてみたいと思います。

ではでは、また次回。

Docker Swarmモードでのnode管理

どうも、nippa です。

Docker Swarm モードを利用していますが、ノードの管理方法を記事にまとめておこうと思います。

環境

Virtual Box 上に Linux サーバを 2 台構築

  • ubuntu 20.04
  • メモリ:2GB
  • ストレージ: 10GB

サーバは

  • Manager サーバ(manager node + work node)
  • Worker サーバ(work node)

と定義します。()の中は swarm モードでの node 構成を示しています。

設定の詳細はこちらの記事に記載してあります。

timesaving.hatenablog.com

node 一覧表示

以下のコマンドで、ノード一覧を表示します。

docker node ls

node の詳細確認

以下のコマンドで、アクセス中 manager node の swarm モードの詳細を確認することができます。

docker node inspect self --pretty

他のノードの詳細を確認する場合は、

docker node ls

で表示されるIDまたはHostnameを指定すると詳細を確認することができます。

docker node inspect [ID or Hostname] --pretty

manager node の Drain

manager node を Swarm モードから Drain する場合、

docker node update --availability drain [ID or Hostname]

IDまたはHostnameを指定することで Drain にすることができます。

manager node の active

manager node を Swarm モードから Drain する場合、

docker node update --availability active [ID or Hostname]

IDまたはHostnameを指定することで Active にすることができます。

manager node への昇格

以下のコマンドで、指定した node を manager node へ昇格することができます。

docker node promote [ID or Hostname]

manager node からの降格

以下のコマンドで、指定した node を manager node へ降格することができます。

docker node demote [ID or Hostname]

node のメタ情報の追加

--label-add [key]で、指定したノードにラベル() を追加することができます。

docker node update --label-add database [ID or Hostname]

ノードのラベルの確認には、docker node inspect [ID or Hostname] --pretty

複数指定することも可能です。また、<key>=<value>という形でラベルを指定することもできます。

node のメタ情報の削除

以下のコマンドでラベルを上書きして削除します。

docker node update [ID or Hostname]

Swarm からノードの除外

以下のコマンドを各ノードで実行することで swarm モードから除外されます。

docker swarm leave

manager node から除外を実行する場合は、以下のコマンドで行います。

docker node rm [ID or Hostname]

感想

今回、ノード管理についてまとまめました。

ラベルをつけることで、ラベルのついた node へのコンテナをデプロイすることができるようになります。

ではでは、また次回。

Python Seleniumスクレイピング

どうも、nippa です。

スクレイピングの必要性が出てきたので、Selenium で Web 操作を自動化したいと思います。

Web 開発でのモンキーテストにも使えるので、知っていて損はないかと思います。

Web サイトによってはスクレイピングが禁止されているところもありますので、ご注意ください。

環境

パッケージ管理

パッケージは poetry を使って管理していきます。利用するパッケージは

の2つです。

以前までは、PC にインストールされている Chrome のブラウザのメジャーバージョンを調べ、そのバージョンにあったドライバを用意する必要がありました。

chromedriver-binary-autoは自動でインストールされている Chrome のバージョン取得して、それに合うドライバをインストールしてくれます。

以下のコマンドでインストールを行います。

poetry add selenium chromedriver-binary-auto

スクレイピング

今回は自動で Google 検索を行って、検索内容について 100 件取得することを題材とします。

SeleniumGoogle.com の表示

まずは、Selenium の簡単な使い方になります。 url を指定して、Chrome で開き、5 秒スリーブごにブラウザを閉じるコードになります。

import time

import chromedriver_binary  # noqa
from selenium import webdriver
from selenium.webdriver.chrome.options import Options


if __name__ == "__main__":
     base_url = "https://www.google.com/"

    url = f"{base_url}/"

    options = Options()

    # Browser
    driver = webdriver.Chrome(options=options)
    driver.get(url)

    time.sleep(5)

    driver.close()

SeleniumGoogle 検索

Google 検索で、「python selenium」を検索してみます。

スクレイピングでは html の構造を理解している必要があります。

Chrome であれば、Developer Tools を使うことで、html の構造を確認することができます。

また、beautifulsoup4 ライブラリを使えば html の構造ごと取得することもできます。

import sys
import time

import chromedriver_binary  # noqa
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

if __name__ == "__main__":
    base_url = "https://www.google.com/"

    url = f"{base_url}/"

    options = Options()

    driver = webdriver.Chrome(options=options)
    driver.get(url)

    name = "q"  # elementのname
    search_word = "python selenium"  # 検索するワード
    try:
        marker = driver.find_element(By.NAME, name)  # elementの取得
        marker.send_keys(search_word)  # elementに検索ワードの送信
        marker.send_keys(Keys.ENTER)  # elementにenterのkeyコマンドを送信

    except Exception as err:
        print(err)
        sys.exit(1)

    time.sleep(5)

    driver.close()

Google.com の検索用の input の name はqのため name を指定して、element を取得しています。

取得した element に検索するワードを送り、Enter key コマンドを送付して、検索を行います。

検索結果表示後、5秒間スリーブして、ブラウザを閉じるという動作になっています。

検索結果のタイトルと URL の取得

xpathを使って、検索結果のタイトルと URL を取得します。

google の検索結果の html の構造は以下のようになっています

...
<dvi id="search">
  <dvi>
    ...
    <a hred="リンク">
      <h3>タイトル</h3>
    </a>
    ...
  </dvi>
</dvi>

このときのタイトルを取得するためのxpathの書き方としては、//div[@id='search']//a/h3となります。

//は複数の element 意味します。

また親の element を取得する場合は..で一つ上のxpathを意味します。

import sys
import time

import chromedriver_binary  # noqa
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

if __name__ == "__main__":
    base_url = "https://www.google.com/"

    url = f"{base_url}/"

    options = Options()

    # Browser
    driver = webdriver.Chrome(options=options)
    driver.get(url)

    name = "q"
    search_word = "python selenium"

    try:
        marker = driver.find_element(By.NAME, name)
        marker.send_keys(search_word)
        marker.send_keys(Keys.ENTER)

        xpath = "//div[@id='search']//a/h3"  # 検索結果のtitleのxpath
        title_elements = driver.find_elements(By.XPATH, xpath)  # 結果の取得

    except Exception as err:
        print(err)
        sys.exit(1)

    for title_element in title_elements:
        if len(title_element.text) > 0:
            # 検索結果のtitleの親のxpathを取得
            href_element = title_element.find_element(By.XPATH, "..")
            # 出力
            print(
                f'titele={title_element.text.replace(" ...", "")} url={href_element.get_attribute("href")}'
            )

    time.sleep(5)

    driver.close()

これで検索 1 ページ目の検索結果のタイトル(一部)と URL を取得することができます。

複数のページの検索結果のタイトルと URL の取得

複数のページの検索結果を取得する場合、ページ移動を挟んで取得していきます。

ページ移動にはリンクの URL を取得して、そのページをロードする形にし、そのページが表示されたら、データを取得します。

xpath = "//div[@id='botstuff']//a[@id='pnnext']"
next_element = driver.find_element(By.XPATH, xpath)
next_page_url = next_element.get_attribute("href")
driver.get(next_page_url)

実際に pythonスクリプト化すると以下のようになります。

以下ではpython seleniumという検索ワードで 10 ページ分の検索結果をタイトル、url を表示するものになっています。

また、--headlessオプションを利用しています。

from typing import List, TypedDict, Optional
import sys
import time

import chromedriver_binary  # noqa
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys


class ResultData(TypedDict):
    title: Optional[str]
    url: Optional[str]


def get_title_and_url(driver: webdriver.Chrome) -> List[ResultData]:
    data: List[ResultData] = []

    try:
        xpath = "//div[@id='search']//a/h3"
        title_elements = driver.find_elements(By.XPATH, xpath)
    except Exception as err:
        print(err)
        sys.exit(1)

    for title_element in title_elements:
        if len(title_element.text) > 0:
            href_element = title_element.find_element(By.XPATH, "..")
            result_data: ResultData = {
                "title": title_element.text.replace(" ...", ""),
                "url": href_element.get_attribute("href"),
            }

            data += [result_data]

    return data

if __name__ == "__main__":
    base_url = "https://www.google.com/"

    url = f"{base_url}/"

    options = Options()
    options.add_argument("--headless")

    # Browser
    driver = webdriver.Chrome(options=options)
    driver.get(url)

    search_word = "python selenium"
    max_page = 10

    try:
        name = "q"
        marker = driver.find_element(By.NAME, name)
        marker.send_keys(search_word)
        marker.send_keys(Keys.ENTER)

    except Exception as err:
        print(err)
        sys.exit(1)

    data: List[ResultData] = []

    for i in range(max_page):
        data += get_title_and_url(driver)

        time.sleep(1)
        if i < max_page - 1:
            xpath = "//div[@id='botstuff']//a[@id='pnnext']"
            next_element = driver.find_element(By.XPATH, xpath)
            next_page_url = next_element.get_attribute("href")
            driver.get(next_page_url)

    driver.close()
    for _data in data:
        print(f'titele={_data["title"]} url={_data["url"]}')

感想

今回、Google の検索結果をスクレイピングselenium を使って書いてみました。

Web テスト用として利用する準備として、今回試しに使ってみています。

スクレイピングはサイトによっては禁止されていますので、サイトのルールに則ってください。

ではでは、また次回。

Docker Swarmモードの設定

どうも、nippa です。

docker をマルチサーバで共通して使いたいと思い、 swarm モードを利用してみようと思います。

今回は swarm モードの設定をになります。

Docker Swarm モードとは

docker エンジンを使った、クラスター管理モードです。マルチサーバ上で、docker を利用する場合に適しています。

また、docker ベースなので、サーバ環境の移行や、スケールリングが比較的容易にできる点が非常に便利です。

環境

Virtual Box 上に Linux サーバを 2 台構築

  • ubuntu 20.04
  • メモリ:2GB
  • ストレージ: 10GB

サーバは

  • Manager サーバ(manager node + work node)
  • Worker サーバ(work node)

と定義します。()の中は swarm モードでの node 構成を示しています。

ホストの登録

サーバが相互でホスト参照できるように/etc/hostsにホスト情報を登録しておきます。

sudo vim /etc/hosts

# 以下の形式で両サーバに登録する
[ホスト1のIP] [ホスト名1]
[ホスト2のIP] [ホスト名2]

Manager サーバ Swarm モードの設定

Swarm がアクティブになっているかを確認します。

以下のコマンドを実行して、Swarm: activeになっているかを確認します。

docker system info | grep Swarm

Swarm: inactiveの場合は、以下のコマンドで swarm モードをアクティブにします。

docker swarm init

ネットワーク接続が 2 つ以上の場合は、swarm モードで利用する IP を指定する必要があります。

docker swarm init --advertise-addr [IPアドレス]

swarm モードがアクティブになると、以下のような出力がされます。

Swarm initialized: current node ([node ID]) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token [SWARMトークン] [IPアドレス]:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

node の設定状況は

docker node ls

で確認できます。

これで Manager サーバの設定は完了になります。

Worker サーバの追加

ホスト間の通信プロトコルは以下のようになっています。

ポート 通信方式 用途
2377 TCP クラスター管理における通信
7946 TCP ノード間の通信のため
4789 UDP オーバーレイネットワークのトラフィックのため
50 EPP オーバーレイネットワークを暗号化(--opt encrypted)で利用する場合

ホスト間でのプロトコルとポートの開放

上記については、サーバ間の通信の許可しておく必要があります。

worker サーバで、swarm モードがアクティブになった際に表示されたコマンドを実行して、swarm モードに worker サーバを追加します。

# workerサーバ
docker swarm join --token  [SWARMトークン] [IPアドレス]:2377

worker サーバが複数ある場合は、各サーバで swarm モードへの追加をコマンドを実行してください。

トークン、IP の確認は、Manager サーバで

# managerサーバ
docker swarm join-token manager

を実行すると確認できます。

また、node が追加されたことは、manager サーバで確認できます。

# managerサーバ
docker node ls

# 出力
ID                            HOSTNAME   STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
rnlr2ria5qdkz07uspzgnm5lq *   manager    Ready     Active         Leader           20.10.14
z73grf2bdca0mkzz8j44ep7sm     worker     Ready     Active                          20.10.14

サービスのデプロイ

docker service createでサービスを作成します。

docker service create --replicas 1 --name helloworld alpine ping docker.com

manager サーバでサービスの状態の確認をします。

# managerサーバ
docker service ls

サービスの詳細は以下で確認します。

docker service inspect --pretty [サービス名]

サービスの詳細は以下で確認します。

docker service ps helloworld

サービスのスケール変更

サービスのスケール変更は以下のコマンドで行います。

# スケールの変更
docker service scale <サービスID>=<タスク数>

# helloworld を実行する node を5とする場合
docker service scale helloworld=5

サービスのノードが変わったことは以下で確認できます。

docker service ps helloworld

サービスの削除

以下のコマンドでサービスを削除します。

docker service rm helloworld

サービスが削除されるとサービス詳細を確認すると、エラーになることを確認します。

docker service inspect helloworld

# 出力
[]
Error: no such service: helloworld

感想

今回 docker swarm モードについてまとめました。

クラスターで利用する場合に、非常に便利なモードです。

swarm モードの実践的なデプロイ方法やネットワーク関連をまとめたいと思います。

ではでは、また次回。