20220223のdockerに関する記事は5件です。

これであなたもDockerプロフェッショナル ~Dockerを使用してNginx経由でDjangoを動かしてみようの巻~

自己紹介 普段私は、 一番得意な機械学習(深層学習)をしたり、 Python/Django でWebアプリを開発したり、 TypeScript/Vue or React でフロントエンドの開発をしたり、 PHP/Laravel でWebアプリを開発したり、 さまざまなことを行っています。 趣味で休みの日にGo言語で色々作成しているのですが、型のある世界は素敵だなと昨今感じています。 今最もやりたいことは、Goで大規模なWebアプリケーションを作成したい。 企業案件やご連絡等ございましたらお気軽に下記よりご連絡いただければと思います。 nagamatsu-k@dym.jp 今回やりたいこと Dockerで構築したコンテナ上で、Nginx経由でDjangoのプロジェクトが見れるようにしたい。 下記コマンドでダウンロードしてください (注意).envの設定があるので、クローン後に下記の.env2箇所を指示通り変更してください。 私のGitHub $ git clone https://github.com/kouhei-github/Docker-Django-Nginx-Gunicorn.git Usage(使い方) ・ dockerを立ち上げる $ docker compose up -d ・ Dockerfileを修正した際など、Imageをbuildし直すコマンド $ docker compose build ・ dockerコンテナの容量が大部分を占め、一旦削除したいとき $ docker prune -a Docker関連の説明 I. 大前提 Dockerfileや関連ファイルはdockerディレクトリの中に格納 Djangoのコードはdjangoという名前でコンテナのコードとマウントさせている ディレクトリ構造 ├── docker │   ├── api │   │   ├── Dockerfile │   │   └── requirements.txt │  ├── front │   │   └── Dockerfile │   ├── mysql │   │   ├── my.cnf │   └── nginx │   └── conf.d │   └── default.conf ├── django ├── nuxt └── docker-compose.yml II. DjangoのDockerfile Djangoを動かすための環境を用意する。 # 使用するPythonイメージを記述 FROM python:3.9-slim-buster # 環境変数の設定 ENV PYTHONIOENCODING utf-8 ENV TZ="Asia/Tokyo" ENV LANG=C.UTF-8 ENV LANGUAGE=en_US:en # ワーキングディレクトリを指定 WORKDIR /var/www/html/pdf-to-csv # requirements.txtをコピー COPY ./docker/api/requirements.txt /tmp/requirements.txt # mysqlclientをインストールする際に必要なツールのインストール RUN apt-get update && \ apt-get -y install gcc libmariadb-dev # requirements.txtをインストール RUN pip install -r /tmp/requirements.txt Dockerfileの中身の補足 MySQLを使用する際はgccコンパイラとlibmariadb-devのインストールが必要になる。 # mysqlclientをインストールする際に必要なツールのインストール RUN apt-get update && \ apt-get -y install gcc libmariadb-dev III. DockerでMySQLを使用するための設定 下記設定はなくても良いがDBのデータを永続化させたい場合は下記ディレクトリが必須。 なぜならdocker-composeでDBの永続化先を./docker/mysqlに指定しているから ./docker/mysql/my.cnf [mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci [client] default-character-set=utf8mb4 IV. DockerでNginxを使用するための設定 Dockerfileを作成しても良いがImageを指定して、 defaukt.confをコンテナにコピーするだけなのでdocker-compose.ymlに書くことにし。 ./docker/nginx/conf.d/default.conf upstream django_app { server django:8000; } server { listen 80; location / { proxy_pass http://django_app; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; } location /static/ { autoindex on; alias /django/static/; } location /media/ { autoindex on; alias /django/media/; } } 下記二箇所は注意 upstreamの名前を任意のものに変更し、serverをservice名:ポート番号にする upstream django_app { # docker-compose.ymlのdjangoが動いているservice名:ポート番号 server django:8000; } proxy_passをhttp://upstream名にする location / { # upstreamの名前を http://hogehoge; にする proxy_pass http://django_app; : : 省略 : } djangoのstaticファイルをNginxのコンテナにマウントしているので、そのパスと合わせる location /static/ { autoindex on; # マウントしているstaticディレクトリのパス alias /django/static/; } location /media/ { autoindex on; # マウントしているmediaディレクトリのパス alias /django/media/; } V. .envファイルの作成 ./.env USERNAME=test-USERNAME USERPASS=test-USERPASS DATABASE=test-DATABASE ROOTPASS=test-ROOTPASS .envファイルの中身をdocker-compose.ymlで使用する際は、 下記の書き方で取り出せる ${USERNAME} ${USERPASS} ${DATABASE} ${ROOTPASS} VI. docker-compose.ymlについて version: "3" services: django: build: context: . dockerfile: ./docker/api/Dockerfile volumes: - ./django:/var/www/html/pdf-to-csv - ./django/static:/var/www/html/pdf-to-csv/static - ./django/media:/var/www/html/pdf-to-csv/media command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 # command: python3 -u manage.py runserver 0.0.0.0:8000 ports: - "8000:8000" tty: true depends_on: - db nginx: image: nginx container_name: nginx volumes: - ./django/static:/django/static - ./django/media:/django/media - ./docker/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf tty: true ports: - "80:80" - "443:443" depends_on: - django # MySQL db: image: mysql:5.7 container_name: test-mysql environment: MYSQL_ROOT_PASSWORD: ${ROOTPASS} MYSQL_DATABASE: ${DATABASE} MYSQL_USER: ${USERNAME} MYSQL_PASSWORD: ${USERPASS} TZ: 'Asia/Tokyo' command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci volumes: - ./docker/mysql/data:/var/lib/mysql - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf - ./docker/mysql/sql:/docker-entrypoint-initdb.d ports: - "3306:3306" # phpMyAdmin phpmyadmin: container_name: test-phpmyadmin image: phpmyadmin/phpmyadmin environment: - PMA_ARBITRARY=1 - PMA_HOSTS=test-mysql - PMA_USER=root - PMA_PASSWORD=${ROOTPASS} ports: - "8080:80" Nginxを使用することからgunicorn経由で呼び出す 下記コマンドではなく $ python3 -u manage.py runserver 0.0.0.0:8000 gunicornでDjangoのアプリを起動する日長がある gunicorn config.wsgi:application --bind 0.0.0.0:8000 Django側の設定 djangoのアプリケーションのソースコードにDBの接続情報を載せたくないので.envファイルを作成する # MySQL # DATABASE_URL=mysql://USERNAME:USERPASS@docker-compose.ymlのDBのコンテナ名:3306/DATABASE DATABASE_URL=mysql://dreamer:jamnuvu)$hna@09ss@db:3306/tuchiya-pdf-to-csv SECRET_KEY=-シークレットキー DEBUG=True ALLOWED_HOSTS =[127.0.0.1] DOMAIN_ROOT=/var/www/html/pdf-to-csv/ SERVICE_ACCOUNT_JSON=secret.json # EMAIL EMAIL_HOST_USER=test@testt.com EMAIL_HOST_PASSWORD=hogehoge
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go の Gorm 使う

はじめに Goを勉強し始めたということで、O/Rマッパーも体験しておきます。 今回は、Go で API 作るの続きとして、Gormを入れて試してみたいと思います。 早速作ってみる 今回もこれをベースに組み込んでいきます。 Go で API 作るの続きなので、API部分はginを使います。 ※ginの使い方は、上記Qiita記事で紹介しているので割愛します。 Gorm を取得する Dockerで開発しているので、docker composeコマンドとなります。 DBはPostgreSQLを使ってみます。 $ docker compose run --rm back go get -u gorm.io/gorm $ docker compose run --rm back go get -u gorm.io/driver/postgres DB と接続 取得したgormとpostgresをimport cmd/main.go import ( + "gorm.io/driver/postgres" + "gorm.io/gorm" ) DBと接続するメソッドを作成します。 cmd/main.go func gormConnect() *gorm.DB { dsn := os.Getenv("DATABASE_URL") db, _ := gorm.Open(postgres.Open(dsn), &gorm.Config{}) db.AutoMigrate(&Todo{}) return db } Herokuにデプロイも試したかったので、dsnは、DATABASE_URLの環境変数から取るようにしました。 DATABASE_URL=host=postgres user=postgres password=password dbname=go-todo-api-db port=5432 sslmode=disable TimeZone=Asia/Tokyo todosテーブルは、AutoMigrateを使って作られるようにしました。 db.AutoMigrate(&Todo{}) todosテーブルはこんな感じで定義しています。 cmd/main.go type Todo struct { gorm.Model Title string } リスト取得(GET: /api/todos)を作ってみる 上記のようにルートを用意し、getTodosメソッドが呼ばれるようにします。 Railsで言う、Todo.allのように、DBから取得します。 cmd/main.go func getTodos(c *gin.Context) { var todos [] Todo db := gormConnect() db.Find(&todos) c.JSON(http.StatusOK, gin.H { "todos": todos }) } Findメソッドを使います。 データが空なので、何も帰ってこないと分かりづらいです。 試しに2つデータを登録して、GETで/api/todosを叩いてみました。 todosテーブルを作成する際に使っているTodoの型には、Titleカラムしか指定していないのに、ID、CreatedAt、UpdatedAt、DeletedAtカラムが追加されています。 これは、gorm.Modelに定義されているそうです。 デフォルトでIDカラムは主キーで、自動でインクリメントもしてくれました。 ちなみにIDカラム以外の独自に定義したカラムを主キー且つ、自動インクリメントしたい場合は、以下のようにすると出来ました。 Id uint `gorm:"primaryKey;autoIncrement` Title string リスト追加(POST: /api/todos)を作ってみる 上記のようにルートを用意し、addTodoメソッドが呼ばれるようにします。 cmd/main.go func addTodo(c *gin.Context) { type InputTodo struct { Title string } var inputTodo InputTodo c.BindJSON(&inputTodo) newTodo := Todo { Title: inputTodo.Title } db := gormConnect() db.Create(&newTodo) c.JSON(http.StatusOK, gin.H { "todo": newTodo }) } Createメソッドを使います。 リクエストを投げるだけでは分かりづらいので、追加したTodoをレスポンスとして返すようにしてみました。 POSTで/api/todosを叩いてみます。 IDも自動インクリメントされているし、渡したTitleが入ったデータが作られていそうです。 リスト更新(PATCH: /api/todos/:ID)を作ってみる 上記のようにルートを用意し、updateTodoメソッドが呼ばれるようにします。 ※パラメタの受け取り方をIdからIDに変更しました。(r.PATCH("/api/todos/:ID", updateTodo)) cmd/main.go func updateTodo(c *gin.Context) { type InputTodo struct { Title string } Id := c.Param("ID") id, _ := strconv.Atoi(Id) db := gormConnect() var todo Todo db.First(&todo, id) var inputTodo InputTodo c.BindJSON(&inputTodo) db.Model(&todo).Update("Title", inputTodo.Title) c.JSON(http.StatusOK, gin.H { "todo": todo }) } Updateメソッドを使います。 FirstやFindで取得した後、カラム毎に値を代入して、Saveもできるそうです。 PATCHで/api/todos/1を叩いてみます。 渡したTitleでIDが1のレコードが更新されていそうです。 リスト削除(DELETE: /api/todos/:ID)を作ってみる 上記のようにルートを用意し、deleteTodoメソッドが呼ばれるようにします。 ※パラメタの受け取り方をIdからIDに変更しました。(r.DELETE("/api/todos/:ID", deleteTodo) cmd/main.go func deleteTodo(c *gin.Context) { Id := c.Param("ID") id, _ := strconv.Atoi(Id) db := gormConnect() var todo Todo db.Delete(&todo, id) } Deleteメソッドを使います。 DELETEで/api/todos/1を叩いてみます。 削除されたことが分かりづらいので、GETで/api/todosも叩いてみます。 IDが1のレコードが削除されていそうです。 おまけ(Cross-Domain問題) 今回紹介した方法で、リクエストを送っているツールは、Insomniaを使っています。 仮に、フロントエンドアプリからリクエストを送るとしたら、Cross-Domain問題が発生すると思います。 その場合は、corsの設定を行います。 $ docker compose run --rm back go get -u github.com/gin-contrib/cors import ( + "github.com/gin-contrib/cors" ) r := gin.Default() +r.Use(cors.New(cors.Config{ + AllowOrigins: []string{os.Getenv("CORS_ORIGIN")}, + AllowMethods: []string{"PUT", "PATCH", "DELETE"}, +})) GETとPOSTまでは問題なかったですが更新のPATCHあたりからこの問題に直面しました。 適宜、使用するHTTPリクエストメソッドを追加してください。 おまけ(HerokuのGoのバージョン認識) 今回使用しているGoのバージョンは、1.17.1です。 筆者は、DockerをそのままHerokuにデプロイしていますが、ライブラリインストールでエラーが発生しました。 ../codon/tmp/cache/go-path/pkg/mod/gorm.io/gorm@v1.23.1/logger/sql.go:105:45: rv.IsZero undefined (type reflect.Value has no field or method IsZero) note: module requires Go 1.14 Goのバージョンを1.14以降にしたら良さそうですが、1.17.1の想定です。 go.modにも1.17と記載されています。 HerokuのBuild Logを遡ってみると、1.12.17を使っていることが判明 -----> !! The go.mod file for this project does not specify a Go version !! !! Defaulting to go1.12.17 !! !! For more details see: https://devcenter.heroku.com/articles/go-apps-with-modules#build-configuration !! -----> Using go1.12.17 Herokuの環境変数に、GOVERSION=1.17.1を追加すると解決しました。 おわりに 今回のソースはこちらのGitHubに上げてます。 参考文献 GORMガイド Go言語でORM触ってみた - Qiita
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Archlinux dockerコンテナをターミナル開発環境として使うときのトラブル色々 1

archlinuxコンテナをターミナル開発環境として使う dockerコンテナをいろいろと開発用ツールを詰め込んだ作業用環境として使ってます。 パッケージを入れたり外したりする関係で、簡単にゼロクリアできる開発環境が必要なので重宝しているのですが、、結構とらぶります。 そもそもコンテナは軽量化のために対話で使う際の機能なんかはかなりごっそりと切り落としていますし、セキュリティの観点で権限管理も固く作られているので、通常利用するときのようにホイホイ一般ユーザを作ってそのユーザで作業しようとするきにけっつまづくとか。 これまではubuntuコンテナこの役割を担わせていたのですが、最近ホスト自体をarchlinuxに差し替えてみたこともあり、このターミナル開発環境コンテナもarchlinux化してみました。 案の定、色々トラブったので、そこら辺のtipsをまとめます。 前提条件 archlinux:latestに対して、一般ユーザを作成し、sudoを導入して、通常時は一般ユーザで色々作業するようにしてます。 トラブル色々 manが使えない man-dbをインストールしても、manが通りません。 結論からいうと、/etc/pacman.confが原因です。 % man ls No manual entry for ls archlinuxコンテナの設定では、pacmanでパッケージを導入した際、manなどのドキュメント系のファイルは導入しないようにする設定が入っています。 % cat /etc/pacman.con ... [options] NoExtract = usr/share/help/* !usr/share/help/en* NoExtract = usr/share/gtk-doc/html/* usr/share/doc/* NoExtract = usr/share/locale/* usr/share/X11/locale/* usr/share/i18n/* NoExtract = !*locale*/en*/* !usr/share/i18n/charmaps/UTF-8.gz !usr/share/*locale*/locale.* NoExtract = !usr/share/*locales/en_?? !usr/share/*locales/i18n* !usr/share/*locales/iso* NoExtract = !usr/share/*locales/trans* NoExtract = usr/share/man/* usr/share/info/* NoExtract = usr/share/vim/vim*/lang/* 下から2行目ですね。 ただし、これだけ直してもだめです。 この設定した「後」にインストールするパッケージ分はいいのですが、最初から入っているパッケージの分はすでにmanなしでインストールされてしまっているので、改めてインストールし直さないといけないわけですね。 対策としては以下みたいに再インストールする方法もありますが、、 # pacman -S coreutils $ man ls LS(1) User Commands LS(1) NAME ls - list directory contents SYNOPSIS ... まぁきりないので、man-pagesをインストールするのが無難でしょうか。 これで、解決。多分。 Dockerfileに仕込むならこんなでしょうか。 RUN sed -i -e 's|^\(NoExtract *= *usr/share/man/\)|#\1|' /etc/pacman.conf$ pingができない sudoすれば大丈夫なのですが、一般ユーザ権限だとpingが打てません。。 原因は権限管理でした。 % ping localhost % echo $? 2 pingはもともとrootじゃないと実施でいないものを、色々設定で一般ユーザでも実施できるようにしているようで。 以下で勉強させていただきまして。 https://blog.ssrf.in/post/ping-does-not-require-cap-net-raw-capability/ https://tenforward.hatenablog.com/entry/2019/11/27/022547 で。 % sysctl net.ipv4.ping_group_range net.ipv4.ping_group_range = 1 0 man icmpによると、上記は which means no group is allowed to create ICMP Echo socketsの意味とのこと。 一方、ホストにインストールしているarchlinuxを見ると、"net.ipv4.ping_group_range = 0 2147483647"となっていて、これはどのユーザグループもpingを打たせてもらえる、ということだそうで。 ではどこでその設定の差分が出ているかというと、、ちょっとややこしい。 まず、この設定は/usr/lib/sysctl.d/50-default.confに入っています。 このファイルの中身は、ホストにインストールしたものもコンテナも一緒。 一緒なのになぜ動きが違うかというと、、、 # sysctl -p /usr/lib/sysctl.d/50-default.conf sysctl: permission denied on key "kernel.sysrq" sysctl: permission denied on key "kernel.core_uses_pid" sysctl: permission denied on key "net.ipv4.conf.default.rp_filter" sysctl: cannot stat /proc/sys/net/ipv4/conf/*/rp_filter: No such file or directory sysctl: permission denied on key "net.ipv4.conf.all.rp_filter", ignoring sysctl: permission denied on key "net.ipv4.conf.default.accept_source_route" sysctl: cannot stat /proc/sys/net/ipv4/conf/*/accept_source_route: No such file or directory sysctl: permission denied on key "net.ipv4.conf.all.accept_source_route", ignoring sysctl: permission denied on key "net.ipv4.conf.default.promote_secondaries" sysctl: cannot stat /proc/sys/net/ipv4/conf/*/promote_secondaries: No such file or directory sysctl: permission denied on key "net.ipv4.conf.all.promote_secondaries", ignoring sysctl: permission denied on key "net.ipv4.ping_group_range", ignoring sysctl: cannot stat /proc/sys/net/core/default_qdisc: No such file or directory sysctl: permission denied on key "fs.protected_hardlinks" sysctl: permission denied on key "fs.protected_symlinks" sysctl: permission denied on key "fs.protected_regular" sysctl: permission denied on key "fs.protected_fifos" はい。 コンテナではカーネルパラメータがいじれない、、? 残念ながら裏付けは追いきれてませんが。 ちなみにubuntuコンテナでこういうケースってなかったので調べてみたのですが、ubuntu(20.4)は上記のping_group_rangeを使わず、"cap_net_raw+ep"というCapabilityをpingの実行ファイルに付与する方法をとっているようです。 Dockerfileでpingの実行ファイルに上記のCapablilityを付与する処理を追加してもいいのですが、、まぁ、、実害はないので、現状はおいておきます。 とりあえず 今回はこんなところで。 また知見溜まってたら別途書くとして。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLのdumpファイルをインポートすると文字化けする

概要 商用環境(Webシステム)をローカルに構築しように発生した問題です。 解決方法をメモとして残します。 ローカル構築時に行った手順 ソースコードをローカルにコピー MySQL立ち上げ(docker) dumpファイルをMySQLにインポート DB接続先を修正 動作確認 →MySQLのデータが文字化けしてしまっている 対応 dumpファイルのこの行を消す dump_yyyymmdd.sql /*!50503 SET NAMES cp932 */; 感想 商用環境がWindowsサーバーだったり、MySQLのversionがどうこうとか、ローカルがdockerだからとか、 色々想像で動いてしまったせいで解決に時間がかかりました。情けない!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node-REDでVOICEVOXを使い中品質のTTSを試す

はじめに VOICEVOXをkubernetes(arm64)にデプロイしてNode-REDから利用してみました。 ソフトウェアには「中品質」とあるのですが、私には高品質と遜色ないように思えます。 前提環境 いつもの「おうちkubernetesクラスタ」と、arm64コンテナイメージのビルド用としてM1搭載のMac miniを使用しております。 Raspberry Pi 4B kubernetes 1.22.0 Private Registry (http) Mac mini 2020(M1) + Docker Desktop Node-RED v2.2.1 on kubernetes コンテナの作成 Mac mini 2020(M1)でコンテナをビルドします。(※Raspberry Pi 4Bでは試してません) Dockerfile FROM ubuntu:focal AS build ENV DEBIAN_FRONTEND=noninteractive RUN apt update && \ apt -y install wget unzip tar && \ mkdir /voicevox_engine && \ wget https://github.com/VOICEVOX/voicevox_core/releases/download/0.11-preview.0/core.zip && \ unzip core.zip && \ cp /core/metas.json /core/a.bin /core/d.bin /core/s.bin /voicevox_engine && \ cp /core/libcore_cpu_arm64.so /voicevox_engine/libcore.so && \ wget https://github.com/VOICEVOX/onnxruntime-builder/releases/download/1.10.0.1/onnxruntime-linux-arm64-cpu-v1.10.0.tgz && \ tar xzvf onnxruntime-linux-arm64-cpu-v1.10.0.tgz && \ mv onnxruntime-linux-arm64-cpu-v1.10.0 /voicevox_engine FROM ubuntu:focal ENV DEBIAN_FRONTEND=noninteractive RUN apt update && \ apt -y install git pip python3 python3-dev python3-wheel cmake g++ libsndfile1 && \ git clone -b 0.11.preview-0 https://github.com/VOICEVOX/voicevox_engine.git && \ cd voicevox_engine/ && \ pip install -r requirements.txt -r requirements-test.txt COPY --from=build /voicevox_engine /voicevox_engine CMD ["python3","/voicevox_engine/run.py","--voicevox_dir","/voicevox_engine","--runtime_dir","/voicevox_engine/onnxruntime-linux-arm64-cpu-v1.10.0/lib","--host","0.0.0.0"] 上記のブランチやファイルのバージョンの組み合わせ以外(armhfも)はほとんど試しておりません。 うちの環境だと120秒ほどでビルドできました。 % docker build -t 10.0.0.1:30500/voicevox_engine:20220223_arm64 . [+] Building 120.0s (8/8) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 37B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/ubuntu:focal 2.0s => CACHED [build 1/2] FROM docker.io/library/ubuntu:focal@sha256:669e010b58baf5beb2836b253c1fd5768333f0d1dbcb834f7c07a4dc93f474be 0.0s => [build 2/2] RUN apt update && apt -y install wget unzip tar && mkdir /voicevox_engine && wget https://github.com/VOICEVOX/ 17.1s => [stage-1 2/3] RUN apt update && apt -y install git pip python3 python3-dev python3-wheel cmake g++ libsndfile1 && git clon 115.7s => [stage-1 3/3] COPY --from=build /voicevox_engine /voicevox_engine 0.1s => exporting to image 2.0s => => exporting layers 2.0s => => writing image sha256:0835677442abf1d1800637a63294ef55e3563e5163505103d64c237a19790792 0.0s => => naming to 10.0.0.1:30500/voicevox_engine:20220223_arm64 0.0s Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them 大きなイメージができますが、サイズを気にする人はご自身でなんとかしてください。 % docker images REPOSITORY TAG IMAGE ID CREATED SIZE 10.0.0.1:30500/voicevox_engine 20220223_arm64 0835677442ab 7 minutes ago 865MB この状態でもdockerで起動して遊べます。ごく普通の一般のご家庭でしたらここで終わりです。 % docker run --rm -it -p '50021:50021' 10.0.0.1:30500/voicevox_engine:20220223_arm64 Downloading: "https://github.com/r9y9/open_jtalk/releases/download/v1.11.1/open_jtalk_dic_utf_8-1.11.tar.gz" dic.tar.gz: 100%|███████████████████████████████████████████████████████████████████████████████████| 22.6M/22.6M [00:03<00:00, 6.95MB/s] Extracting tar file /usr/local/lib/python3.8/dist-packages/pyopenjtalk/dic.tar.gz Warning: cpu_num_threads is set to 0. ( The library leaves the decision to the synthesis runtime ) WARNING: Since openmp is enabled in this build, this API cannot be used to configure intra op num threads. Please use the openmp environment variables to control the number of threads. INFO: Started server process [1] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:50021 (Press CTRL+C to quit) Private RegistryへのPush 自宅のPrivate Registryはhttpなので、Docker Engineの設定でinsecure-registriesとして登録しておきます。 Pushします。 % docker push 10.0.0.1:30500/voicevox_engine:20220223_arm64 The push refers to repository [10.0.0.1:30500/voicevox_engine] fa548acaf8ff: Pushed 7b7b0f4b4cd6: Pushed 0c20a4bc193b: Layer already exists 20220223_arm64: digest: sha256:f8e22c0bf227f2e06f656f1d0f4c51ba6c636d51b5ca5bf42d8e122c97665833 size: 954 Registryを持ってないkubernetesの方は、tarでアーカイブして適当に圧縮してデプロイ予定のworkerノードにコピーしてloadしてください。 % docker save 10.0.0.1:30500/voicevox_engine:20220223_arm64 > voicevox.tar <kubernetesノードへコピー> $ docker load < voicevox.tar Loaded image: 10.0.0.1:30500/voicevox_engine:20220223_arm64 kubernetesへのデプロイ Serviceのマニフェスト例です。うちでは一応、ネームスペースを作ってNodePortでアクセスできるようにしますが、ご自宅のポリシーで良いと思います。 voicevox-svc.yaml apiVersion: v1 kind: Service metadata: name: voicevox namespace: voicevox labels: app: voicevox spec: type: NodePort ports: - name: http port: 50021 targetPort: 50021 nodePort: 30521 selector: app: voicevox Podのマニフェスト例です。 Private RegistryもNodePortで公開しているので、コンテナイメージの指定は10.0.0.1ではなくlocalhostとしています。 voicevox-pod.yaml apiVersion: v1 kind: Pod metadata: name: voicevox namespace: voicevox labels: app: voicevox spec: containers: - name: voicevox image: localhost:30500/voicevox_engine:20220223_arm64 resources: limits: memory: 1.5Gi ports: - name: http containerPort: 50021 あとはデプロイしてログを確認します。 $ kubectl apply -f voicevox-svc.yaml service/voicevox created $ kubectl apply -f voicevox-pod.yaml pod/voicevox created $ kubectl get all -n voicevox NAME READY STATUS RESTARTS AGE pod/voicevox 1/1 Running 0 3m7s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/voicevox NodePort 10.96.39.50 <none> 50021:30521/TCP 3m14s $ kubectl logs voicevox -n voicevox Downloading: "https://github.com/r9y9/open_jtalk/releases/download/v1.11.1/open_jtalk_dic_utf_8-1.11.tar.gz" dic.tar.gz: 100%|██████████| 22.6M/22.6M [00:02<00:00, 11.0MB/s] Warning: cpu_num_threads is set to 0. ( The library leaves the decision to the synthesis runtime ) INFO: Started server process [1] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:50021 (Press CTRL+C to quit) 私の力不足でスレッド数の変更ができなかったのですが、現時点では動けばいいかな考えているので問題ないです。 (速さを求めるならM1 Mac mini使えば良い事なので...) Node-REDフローの作成 入力テキストを音声ファイルで出力するVOICEVOXサブフローを作りました。 処理内容はhttpリクエストを2回するだけです。 サブフローの環境変数とUIは以下のようにしています。 音声ファイルは、テストでいつもお世話になっている「node-red-contrib-play-audio」のノードでブラウザ上から再生します。 デバッグのタイムスタンプでinjectから再生開始までの時間を確認すると、10秒程度で音声ファイルが生成できています。 サブフローとテストのフローはここに貼っておきます。 [ { "id": "32238322.9dad8c", "type": "subflow", "name": "今何時?", "info": "\n**msg.hour**:時を代入します。(数値)\n\n**msg.min**:分を代入します。(数値)\n\n**msg.year**:年を代入します。(数値)\n\n**msg.month**:月を代入します。(数値)\n\n**msg.day**:日を代入します。(数値)\n\n**msg.dow**:曜日を代入します。(英語文字列)", "category": "", "in": [ { "x": 60, "y": 60, "wires": [ { "id": "82d3f0aa.7008e" } ] } ], "out": [ { "x": 480, "y": 60, "wires": [ { "id": "fb2dc2fd8e339d6c", "port": 0 } ] } ], "env": [ { "name": "ZONE", "type": "str", "value": "+0900", "ui": { "icon": "font-awesome/fa-clock-o", "label": { "en-US": "タイムゾーン" }, "type": "select", "opts": { "opts": [ { "l": { "en-US": "Asia/Tokyo" }, "v": "+0900" } ] } } } ], "meta": {}, "color": "#C0DEED", "icon": "font-awesome/fa-clock-o" }, { "id": "82d3f0aa.7008e", "type": "change", "z": "32238322.9dad8c", "name": "", "rules": [ { "t": "set", "p": "zone", "pt": "msg", "to": "ZONE", "tot": "env" }, { "t": "set", "p": "millis", "pt": "msg", "to": "$millis()", "tot": "jsonata" }, { "t": "set", "p": "hour", "pt": "msg", "to": "$number($fromMillis(millis,'[H01]',zone))", "tot": "jsonata" }, { "t": "set", "p": "min", "pt": "msg", "to": "$number($fromMillis(millis,'[m01]',zone))", "tot": "jsonata" }, { "t": "set", "p": "dow", "pt": "msg", "to": "$fromMillis(millis,'[F]',zone)", "tot": "jsonata" }, { "t": "set", "p": "year", "pt": "msg", "to": "$number($fromMillis(millis,'[Y]',zone))", "tot": "jsonata" }, { "t": "set", "p": "month", "pt": "msg", "to": "$number($fromMillis(millis,'[M]',zone))", "tot": "jsonata" }, { "t": "set", "p": "day", "pt": "msg", "to": "$number($fromMillis(millis,'[D]',zone))", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 200, "y": 60, "wires": [ [ "fb2dc2fd8e339d6c" ] ] }, { "id": "fb2dc2fd8e339d6c", "type": "function", "z": "32238322.9dad8c", "name": "", "func": "\nif(msg.zone == \"+0900\"){\n switch(msg.dow){\n case \"monday\":\n msg.dow = \"月曜日\";\n break;\n case \"tuesday\":\n msg.dow = \"火曜日\";\n break;\n case \"wednesday\":\n msg.dow = \"水曜日\";\n break;\n case \"thursday\":\n msg.dow = \"木曜日\";\n break;\n case \"friday\":\n msg.dow = \"金曜日\";\n break;\n case \"saturday\":\n msg.dow = \"土曜日\";\n break;\n case \"sunday\":\n msg.dow = \"日曜日\";\n break;\n }\n}\n\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 370, "y": 60, "wires": [ [] ] }, { "id": "07838d665898207e", "type": "subflow", "name": "VOICEVOX", "info": "", "category": "", "in": [ { "x": 40, "y": 80, "wires": [ { "id": "6034de0ad57d9569" } ] } ], "out": [ { "x": 850, "y": 80, "wires": [ { "id": "4a20a945c8e116e0", "port": 0 } ] } ], "env": [ { "name": "URL", "type": "str", "value": "http://10.0.0.1:30521", "ui": { "label": { "ja": "URL" }, "type": "input", "opts": { "types": [ "str" ] } } }, { "name": "SPEAKER", "type": "str", "value": "3", "ui": { "label": { "ja": "キャラクター" }, "type": "select", "opts": { "opts": [ { "l": { "ja": "四国めたん(ノーマル)" }, "v": "2" }, { "l": { "ja": "四国めたん(あまあま)" }, "v": "0" }, { "l": { "ja": "四国めたん(ツンツン)" }, "v": "6" }, { "l": { "ja": "四国めたん(セクシー)" }, "v": "4" }, { "l": { "ja": "ずんだもん(ノーマル)" }, "v": "3" }, { "l": { "ja": "ずんだもん(あまあま)" }, "v": "1" }, { "l": { "ja": "ずんだもん(ツンツン)" }, "v": "7" }, { "l": { "ja": "ずんだもん(セクシー)" }, "v": "5" }, { "l": { "ja": "春日部つむぎ(ノーマル)" }, "v": "8" }, { "l": { "ja": "雨晴はう(ノーマル)" }, "v": "10" }, { "l": { "ja": "波音リツ(ノーマル)" }, "v": "9" } ] } } } ], "meta": {}, "color": "#DDAA99" }, { "id": "4c110c0d3cddaff5", "type": "http request", "z": "07838d665898207e", "name": "", "method": "use", "ret": "txt", "paytoqs": "ignore", "url": "", "tls": "", "persist": false, "proxy": "", "authType": "", "senderr": false, "credentials": {}, "x": 370, "y": 80, "wires": [ [ "82e35d27c239aca1" ] ] }, { "id": "6034de0ad57d9569", "type": "change", "z": "07838d665898207e", "name": "", "rules": [ { "t": "set", "p": "method", "pt": "msg", "to": "POST", "tot": "str" }, { "t": "set", "p": "url", "pt": "msg", "to": "URL", "tot": "env" }, { "t": "change", "p": "url", "pt": "msg", "from": "$", "fromt": "re", "to": "/audio_query?speaker=", "tot": "str" }, { "t": "change", "p": "url", "pt": "msg", "from": "$", "fromt": "re", "to": "SPEAKER", "tot": "env" }, { "t": "set", "p": "payload", "pt": "msg", "to": "$encodeUrlComponent(text)", "tot": "jsonata" }, { "t": "change", "p": "payload", "pt": "msg", "from": "^", "fromt": "re", "to": "&text=", "tot": "str" }, { "t": "change", "p": "url", "pt": "msg", "from": "$", "fromt": "re", "to": "payload", "tot": "msg" }, { "t": "delete", "p": "payload", "pt": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 180, "y": 80, "wires": [ [ "4c110c0d3cddaff5" ] ] }, { "id": "82e35d27c239aca1", "type": "change", "z": "07838d665898207e", "name": "", "rules": [ { "t": "delete", "p": "headers", "pt": "msg" }, { "t": "set", "p": "headers", "pt": "msg", "to": "{\"Content-Type\":\"application/json\"}", "tot": "json" }, { "t": "set", "p": "method", "pt": "msg", "to": "POST", "tot": "str" }, { "t": "set", "p": "url", "pt": "msg", "to": "URL", "tot": "env" }, { "t": "change", "p": "url", "pt": "msg", "from": "$", "fromt": "re", "to": "/synthesis?speaker=", "tot": "str" }, { "t": "change", "p": "url", "pt": "msg", "from": "$", "fromt": "re", "to": "SPEAKER", "tot": "env" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 550, "y": 80, "wires": [ [ "4a20a945c8e116e0" ] ] }, { "id": "4a20a945c8e116e0", "type": "http request", "z": "07838d665898207e", "name": "", "method": "use", "ret": "bin", "paytoqs": "ignore", "url": "", "tls": "", "persist": false, "proxy": "", "authType": "", "senderr": false, "credentials": {}, "x": 730, "y": 80, "wires": [ [] ] }, { "id": "a895e86ec7e60f1c", "type": "subflow:07838d665898207e", "z": "c33d685b.315298", "name": "", "x": 590, "y": 980, "wires": [ [ "3f21056bafb1d156", "f725696724db18ea" ] ] }, { "id": "3f21056bafb1d156", "type": "play audio", "z": "c33d685b.315298", "name": "", "voice": "", "x": 780, "y": 980, "wires": [] }, { "id": "93615a8e104b6ad6", "type": "inject", "z": "c33d685b.315298", "name": "", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 90, "y": 1000, "wires": [ [ "a574c6ae1e9ce8f3", "f725696724db18ea" ] ] }, { "id": "a574c6ae1e9ce8f3", "type": "subflow:32238322.9dad8c", "z": "c33d685b.315298", "name": "", "x": 240, "y": 980, "wires": [ [ "76cf1a5d7739a242" ] ] }, { "id": "76cf1a5d7739a242", "type": "template", "z": "c33d685b.315298", "name": "時報メッセージ", "field": "text", "fieldType": "msg", "format": "handlebars", "syntax": "mustache", "template": "{{hour}}時{{min}}分をお知らせなのだ。", "output": "str", "x": 410, "y": 980, "wires": [ [ "a895e86ec7e60f1c" ] ] }, { "id": "f725696724db18ea", "type": "debug", "z": "c33d685b.315298", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "text", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 490, "y": 1120, "wires": [] } ] おわりに 個人的にVOICEVOXは将来性を感じているので、どんどん成長してほしい製品です。 あと、控えめに言っても「ずんだもん」ちゃんの声は最高に可愛いので、是非みなさんも聴いてほしいです! 音声を投稿する際にはクレジット表記の規約があります。 各キャラクターの利用規約をご確認ください。 ソフトウェアについては、VOICEVOXの利用規約もご確認ください。 GitHubに書かれてあるライセンスもご確認ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む