20211203のdockerに関する記事は19件です。

【再現性皆無】docker開発環境上での、ブラウザテストの設定

bundle exec rails generate rspec:install bin/rails g factory_bot:model mandalart bin/rails g rspec:system mandalart bin/rails g rspec:model mandalart いままでこれらのコマンドを入力してきました。 spec/rails_helper.rb + require 'capybara/rspec' spec/rails_helper.rb require 'capybara/rspec' spec/support/capybara.rb equire 'selenium-webdriver' require 'capybara/rspec' Capybara.configure do |config| config.default_driver = :chrome config.javascript_driver = :chrome config.run_server = true config.default_selector = :css config.default_max_wait_time = 5 config.ignore_hidden_elements = true config.save_path = Dir.pwd config.automatic_label_click = false end Capybara.register_driver :chrome do |app| options = Selenium::WebDriver::Chrome::Options.new options.add_argument('disable-notifications') options.add_argument('disable-translate') options.add_argument('disable-extensions') options.add_argument('disable-infobars') options.add_argument('window-size=1280,960') # ブラウザーを起動する Capybara::Selenium::Driver.new( app, browser: :chrome, options: options ) end spec/rails_helper # This file is copied to spec/ when you run 'rails generate rspec:install' # Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } RSpec.configure do |config| config.before(:each, type: :system) do driven_by :rack_test end config.before(:each, type: :system, js: true) do driven_by :selenium_chrome_headless end end require 'spec_helper' ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../config/environment', __dir__) # Prevent database truncation if the environment is production abort('The Rails environment is running in production mode!') if Rails.env.production? require 'rspec/rails' # Add additional requires below this line. Rails is not loaded until this point! # Requires supporting ruby files with custom matchers and macros, etc, in # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are # run as spec files by default. This means that files in spec/support that end # in _spec.rb will both be required and run as specs, causing the specs to be # run twice. It is recommended that you do not name files matching this glob to # end with _spec.rb. You can configure this pattern with the --pattern # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. # # The following line is provided for convenience purposes. It has the downside # of increasing the boot-up time by auto-requiring all files in the support # directory. Alternatively, in the individual `*_spec.rb` files, manually # require only the support files necessary. # # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } # Checks for pending migrations and applies them before tests are run. # If you are not using ActiveRecord, you can remove these lines. begin ActiveRecord::Migration.maintain_test_schema! rescue ActiveRecord::PendingMigrationError => e puts e.to_s.strip exit 1 end RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = "#{::Rails.root}/spec/fixtures" # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. config.use_transactional_fixtures = true # You can uncomment this line to turn off ActiveRecord support entirely. # config.use_active_record = false # RSpec Rails can automatically mix in different behaviours to your tests # based on their file location, for example enabling you to call `get` and # `post` in specs under `spec/controllers`. # # You can disable this behaviour by removing the line below, and instead # explicitly tag your specs with their type, e.g.: # # RSpec.describe UsersController, type: :controller do # # ... # end # # The different available types are documented in the features, such as in # https://relishapp.com/rspec/rspec-rails/docs config.infer_spec_type_from_file_location! # Filter lines from Rails gems in backtraces. config.filter_rails_from_backtrace! # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") end docker-compose.yml version: '3' services: db: image: mysql:8.0.21 cap_add: - SYS_NICE # コンテナにLinux機能を追加するオプションのようです。SYS_NICEは、プロセスの優先度(nice値)をあげます。 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_HOST: db ports: - '3306:3306' volumes: - mysql-data:/var/lib/mysql command: --default-authentication-plugin=mysql_native_password # 認証方式を8系以前のものにする web: &web build: . command: ./bin/rails s -b 0 stdin_open: true tty: true # この2文を追加でコンテナ内の標準入出力をローカルマシンのターミナルにアタッチする準備が整います。 volumes: - .:/ideaFrameworks ports: - "3000:3000" depends_on: - db - selenium_chrome environment: WEBPACKER_DEV_SERVER_HOST: webpacker MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} RAILS_MASTER_KEY: ${RAILS_MASTER_KEY} MYSQL_HOST: db # selenium_chrome を使うために以下の行を追加 SELENIUM_DRIVER_URL: http://selenium_chrome:4444/wd/hub" selenium_chrome: image: selenium/standalone-chrome-debug logging: driver: none ports: - 4444:4444 webpacker: <<: *web command: ./bin/webpack-dev-server environment: WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 ports: - "3035:3035" volumes: mysql-data: driver: local vendor_bundle: driver: local FROM ruby:2.6.5 ## nodejsとyarnはwebpackをインストールする際に必要 # yarnパッケージ管理ツールをインストール RUN curl http://deb.debian.org/debian/dists/buster/main/binary-amd64/by-hash/SHA256/935deda18d5bdc25fb1813d0ec99b6e0e32a084b203e518af0cf7dc79ee8ebda | head RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn && apt-get install -y graphviz # chromeの追加 RUN apt-get update && apt-get install -y unzip && \ CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \ wget -N http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/ && \ unzip ~/chromedriver_linux64.zip -d ~/ && \ rm ~/chromedriver_linux64.zip && \ chown root:root ~/chromedriver && \ chmod 755 ~/chromedriver && \ mv ~/chromedriver /usr/bin/chromedriver && \ sh -c 'wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' && \ sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' && \ apt-get update && apt-get install -y google-chrome-stable RUN /bin/sh -c /bin/sh -c bundle update --bundler RUN gem install bundler:2.1.4 RUN mkdir /ideaFrameworks WORKDIR /ideaFrameworks COPY Gemfile /ideaFrameworks/Gemfile COPY Gemfile.lock /ideaFrameworks/Gemfile.lock RUN bundle install RUN bundle update rails RUN bundle update RUN bundle update capybara selenium-webdriver #RUN bundle update nokogiri marcel mimemagic RUN bundle install COPY . /ideaFrameworks RUN yarn install --check-files RUN bundle install RUN bundle exec rails webpacker:compile # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"] 僕の、Dockerfileとdocker-compose.ymlの設定では動きました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

proxy環境下でのdocker build

TL; DR dockerfileに書いてしまうと、そのレイヤが保持されてコンテナが作られる。 すると何が起きるかというと、containerをbuildした後にunsetできなくなる。 その回避策。 問題の詳細 以下が、実際に問題となってしまうdockerfileの例です。 # FROM OSの名前:バージョン FROM centos:7 ####### 問題となる部分:ここから ENV HTTP_PROXY http://user:pass@host:port ENV HTTPS_PROXY http://user:pass@host:port ENV FTP_PROXY http://user:pass@host:port ####### 問題となる部分:ここまで RUN echo "now building..." RUN yum update -y && yum upgrade && yum -y install httpd RUN sed -i '/#ServerName/a ServerName www.example.com:80' /etc/httpd/conf/httpd.conf ADD ./index.html /var/www/html/ EXPOSE 80 CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"] こういうdockerfileを書いてしまうと、環境変数HTTP_PROXYに、http://user:pass@host:port がセットされます。 そして、ご存知のようにbuildの過程で1つのレイヤとしてfixしてしまいます。 つまり、環境変数HTTP_PROXYは、あとから変更不可となります 。 例えば、社内でContainerをbuildしてそれを配布したい場合、自分のproxyの認証情報を埋め込んだまま配布してしまいます。 これは問題ですよね。。。。。 回避策 buildする際に、build-arg引数として、proxyの情報を与えてあげましょう。 docker-compose build ^ --build-arg HTTP_PROXY=http://%LDAP%:%PASS%@host:port ^ --build-arg HTTPS_PROXY=http://%LDAP%:%PASS%@host:port ^ --build-arg http_proxy=http://%LDAP%:%PASS%@host:port ^ --build-arg https_proxy=http://%LDAP%:%PASS%@host:port ^ service名 もちろん、この時のdockerfileには、上記のダメサンプルの「問題となる部分」は記載はしません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Win11上のUbuntu on WSL2 + dockerでGPUを使う

初めに WindowsのPCでGPUが使える環境をなんとか仮想環境で実現しようといろいろ考えた結果タイトルのような構成になりました。 スペックは以下の通りです。 CPU: Corei7 (11th gen) GPU: GeForce RTX3080 RAM: 16GB ただし、動かしてみたところメモリは正直増設したほうがいいかもです。。。 手順はざっと以下の通りです。 Win11にアップデート NVIDIAドライバーの導入 WSL2、Ubuntuのセットアップ UbuntuにCUDA導入 Ubuntuにdocker導入 NVIDIAのdockerイメージを動かす では早速行きます。 1. Win11にアップデート この部分は調べれば色々出てくると思いますので、端折ります。 やることとしては Win11入れるための要件のチェックと必要機能の有効化 Win11のインストール となります。1でBIOSいじる必要性などが出てくるの可能性があり少々厄介ですが、頑張りましょう。 2. NVIDIAドライバーの導入 ゲーミングPCなら導入済みかと思います。 参考までに僕のバージョンはGeForce Game Ready Driver 496.76です。 3. WSL2、Ubuntuのセットアップ 以下のコマンドを管理者権限で実行したPowershellに打ち込むだけで導入可能だと思います。 wsl --install -d Ubuntu-20.04 Ubuntuが自動で起動するので、ユーザー名とパスワードを設定しましょう。 一応、ちゃんと導入できているかPowershellのほうで下記コマンドで確認しましょう。 wsl -l -v 下記のように出力されれば大丈夫です。 NAME STATE VERSION * Ubuntu-20.04 Running 2 もし、表示が違っている場合は今インストールしたUbuntuをアプリと機能から探して一度アンインストールし、下記手順でWSL2にアップデートしたうえでUbuntu導入してみてください。 Ubuntuが導入できたら、後続の作業でいろいろ導入する際にエラーが起きないよう、少しだけ下準備をします。 まず、Ubuntuを起動します。exitなど打ち込んで閉じちゃった場合はタスクバーのアプリ検索をすれば出てきます。 起動したら下記コマンドを入力しましょう sudo vim /etc/wsl.conf vimというエディタが起動するのでiを打ち込むとINSERTモード(書き込むモード)に移行できます。左下がINSERTになっていることを確認しましょう。 下記のような記述を追加します。余談ですが、ctrl+vでの貼り付けは受け付けてくれないので、コピーしたらubuntuのターミナル画面で右クリすればペーストできます。 /etc/wsl.conf [network] generateResolvConf = false [boot] command = "sudo echo 'nameserver 8.8.8.8' > /etc/resolv.conf" 追加したら、Escキーを押してINSERTモードから抜け出します。(左下からINSERTの文字が消えます) その後:wqと入力(左下に入力が見えると思います)してEnterを押して保存、終了しましょう。wが保存、qが終了を意味します。 できたら、exitと打ち込んで一度閉じ、もっかいアプリを開きましょう。 下記のように打ち込むと、ちゃんとresolv.confが生成されていることを確認できると思います。 sudo cat /etc/resolv.conf 4. UbuntuにCUDA導入 先ほど導入したUbuntuにCUDAを導入します。ここで、一点留意してほしいのが WSL用のCUDAを導入するということです。 ここからCudaを選んでインストールします。今回は11.3.0を入れます。 上みたいな感じで選ぶと、下にコマンドが出てきますのでこれを打ち込みます。今回の例では以下のようになります。 wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-wsl-ubuntu.pin sudo mv cuda-wsl-ubuntu.pin /etc/apt/preferences.d/cuda-repository-pin-600 wget https://developer.download.nvidia.com/compute/cuda/11.3.0/local_installers/cuda-repo-wsl-ubuntu-11-3-local_11.3.0-1_amd64.deb sudo dpkg -i cuda-repo-wsl-ubuntu-11-3-local_11.3.0-1_amd64.deb sudo apt-key add /var/cuda-repo-wsl-ubuntu-11-3-local/7fa2af80.pub sudo apt-get update sudo apt-get -y install cuda 5. Ubuntuにdocker導入 次に、dockerというアプリの導入を行います。簡単に言うと、dockerさえ入っていればどんなマシンで構築した環境でもすぐにほかのマシンでも再現できるというものです。 docker周りのインストールを自動で行ってくれる便利なコマンド curl https://get.docker.com | sh nvidia-dockerの導入。順番に打ち込みます distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list sudo apt-get update sudo apt-get install -y nvidia-docker2 以下コマンドでユーザーをdockerグループに追加(ユーザーがdockerコマンドを使えるようにする) sudo usermod -aG docker $USER dockerの再起動を行いましょう sudo service docker stop sudo service docker start 6. 実際にdockerイメージを動かす 下記を試しに動かしましょう。 sudo nvidia-docker run --rm -it --gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark -numbodies=512000 GPUで計算が回りますので、下記のような表示ができれば完了です。あとはdockerに少し慣れるだけでいろんなことができるようになります。 ここで、じつはdockerはubuntu起動時に勝手に起動してくれないので、次回起動時にもdockerが自動で立ち上がるようにしましょう。 さっきのwsl.confの内容が以下の通りになるように追記しましょう。 sudo vim /etc/wsl.conf /etc/wsl.conf [network] generateResolvConf = false [boot] command = "sudo echo 'nameserver 8.8.8.8' > /etc/resolv.conf" command = "sudo service docker start" お疲れさまでした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker で十全に gcloud や aws 、cdk コマンドなどを使う方法

インフラエンジニア、 SRE などをしていると gcloud や aws を両方利用するようなことも多いと思います。 それぞれ python 環境に依存しているため、場合によってインストールやアップデート時に苦労したり、 普段 python を使わないがゆえに毎回「venv とは・・・」みたいな気持ちになることもあります。 そこで試しに本記事で紹介する方法で一通りのコマンドを Docker 化してみたところ便利だったので、共有します。 全体としては以下がポイントです aliasではなくシェルスクリプトを$PATHの通ったところに置く uid, gid を引き継ぐ 必要なディレクトリをもれなくマウントしておく ホストとコンテナ上のパスの違いを吸収する ユーザ、または非ユーザとして認証する場合の両方に対応する 公式イメージがないものは docker build も自動実行するようにしておく aliasを使わないのは、別のコマンド、シェルスクリプト内から読んだ場合にも動作させるためです。 また、 docker や今回ご紹介した方法に限った話ではないですが、場合によって重要な情報にアクセスできるような認証情報をコンテナに渡すことになるため、信頼できないソースからビルドしたイメージやコマンドを実行しないようにだけ気をつけてください。 この記事では aws, gcloud, おまけで cdk について紹介します。 この流れでなぜ terraform がないの?と思われるかもしれませんが、シングルバイナリで完結するツールなので、ホストを汚さないというメリットがない分、それほど意味がないと思ったため紹介していません。 とはいえ、インストール方法を統一できるという利点はありそうなので、興味がある方は試してみてください! gcloud PATH の通った適当な場所に以下のような内容で bash スクリプトを置いておきます $HOME/bin/gcloud #!/usr/bin/env bash docker run \ -u `id -u`:`id -g` \ -v ${HOME}/.config/gcloud:/home/cloudsdk/.config/gcloud \ -v $(pwd):$(pwd) \ --workdir $(pwd) -it --rm \ gcr.io/google.com/cloudsdktool/cloud-sdk:latest gcloud "${@}" chmod +x $HOME/bin/gcloud export PATH=$HOME/bin:$PATH これで、ホスト上の python 環境を触らずに、ホスト上に直接 gcloud コマンドをインストールしたような使用感が得られます。 $ gcloud auth login $ gcloud auth list $ gcloud config set account $ACCOUNT # ... # # Service Accountもローカル同様に利用できます # $ gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS $ gcloud auth list # ... docker で gcloud を実行する手順はよく Qiita でも話題になっていますが、完全にホスト上にインストールしたのと同じ使用感のものは見当たらなかったり、サービスアカウントが前提になっているものが多かったので、「以前試したけどうまくいかなかった!」という方はこの方法を試してみていただくといいかもしれませんね。 awscli awscli や AWS CDK のaws や cdk コマンドも同様に Docker 化しておくと、ホストを汚さずにすみ、バージョン管理も楽になったりするのでおすすめです。 aws コマンドの場合は以下のようにしておくと、 aws eks update-kubeconfig コマンドのように $HOME 以下 ($HOME/.kube/config)に書き込むようなコマンドにも対応できます。 $HOME/bin/aws #!/usr/bin/env bash docker run \ -u `id -u`:`id -g` \ -v ~/.aws:/aws \ -v $(pwd):$(pwd) \ --workdir $(pwd) --rm -it \ -e AWS_CONFIG_FILE=/aws/config \ -e AWS_SHARED_CREDENTIALS_FILE=/aws/credentials \ -e AWS_DEFAULT_REGION \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ -e AWS_PROFILE \ -e AWS_ROLE_ARN \ -e AWS_WEB_IDENTITY_TOKEN_FILE \ -e AWS_ROLE_SESSION_NAME \ -e AWS_SESSION_TOKEN \ -e KUBECONFIG=$(pwd)/aws/.kube/config \ amazon/aws-cli:latest \ "$@" おまけ: cdk AWS CDK の cdk コマンドのように、公式にはイメージが配布されていないようなものについては、 docker build のワンライナーを添えておくと、普段どおりコマンドを打つだけでビルドと実行が可能なので便利です。 $HOME/bin/cdk #!/usr/bin/env bash tempdir=$(mktemp -d) || exit 1 cat <<DOCKERFILE | docker build $tempdir -t cdk -f - FROM node:14 RUN npm install -g aws-cdk RUN apt-get update -y && apt-get install less -y RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ unzip awscliv2.zip && \ ./aws/install ENTRYPOINT [ "cdk" ] DOCKERFILE docker run \ -u `id -u`:`id -g` \ -v ~/.aws:/aws \ -v $(pwd):$(pwd) \ --workdir $(pwd) --rm -it \ -e AWS_CONFIG_FILE=/aws/config \ -e AWS_SHARED_CREDENTIALS_FILE=/aws/credentials \ -e AWS_DEFAULT_REGION \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ -e AWS_PROFILE \ -e AWS_ROLE_ARN \ -e AWS_WEB_IDENTITY_TOKEN_FILE \ -e AWS_ROLE_SESSION_NAME \ -e AWS_SESSION_TOKEN cdk "$@" cdk コマンドのようにカレントディレクトリ以下のファイルを読み書きするようなコマンドの場合、 -v $(pwd):$(pwd) や -u オプションでカレントディレクトリ以下にカレントユーザと同じ uid, gid でアクセスできる点が効いてきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker で十全に gcloud や aws コマンドなどを使う方法

インフラエンジニア、 SRE などをしていると gcloud や aws を両方利用するようなことも多いと思います。 それぞれ python 環境に依存しているため、場合によってインストールやアップデート時に苦労したり、 普段 python を使わないがゆえに毎回「venv とは・・・」みたいな気持ちになることもあります。 そこで試しに本記事で紹介する方法で一通りのコマンドを Docker 化してみたところ便利だったので、共有します。 全体としては以下がポイントです aliasではなくシェルスクリプトを$PATHの通ったところに置く uid, gid を引き継ぐ 必要なディレクトリをもれなくマウントしておく ホストとコンテナ上のパスの違いを吸収する ユーザ、または非ユーザとして認証する場合の両方に対応する 公式イメージがないものは docker build も自動実行するようにしておく aliasを使わないのは、別のコマンド、シェルスクリプト内から読んだ場合にも動作させるためです。 また、 docker や今回ご紹介した方法に限った話ではないですが、場合によって重要な情報にアクセスできるような認証情報をコンテナに渡すことになるため、信頼できないソースからビルドしたイメージやコマンドを実行しないようにだけ気をつけてください。 この記事では aws, gcloud, おまけで cdk について紹介します。 この流れでなぜ terraform がないの?と思われるかもしれませんが、シングルバイナリで完結するツールなので、ホストを汚さないというメリットがない分、それほど意味がないと思ったため紹介していません。 とはいえ、インストール方法を統一できるという利点はありそうなので、興味がある方は試してみてください! gcloud PATH の通った適当な場所に以下のような内容で bash スクリプトを置いておきます $HOME/bin/gcloud #!/usr/bin/env bash docker run \ -u `id -u`:`id -g` \ -v ${HOME}/.config/gcloud:/home/cloudsdk/.config/gcloud \ -v $(pwd):$(pwd) \ --workdir $(pwd) -it --rm \ gcr.io/google.com/cloudsdktool/cloud-sdk:latest gcloud "${@}" chmod +x $HOME/bin/gcloud export PATH=$HOME/bin:$PATH これで、ホスト上の python 環境を触らずに、ホスト上に直接 gcloud コマンドをインストールしたような使用感が得られます。 $ gcloud auth login $ gcloud auth list $ gcloud config set account $ACCOUNT # ... # # Service Accountもローカル同様に利用できます # $ gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS $ gcloud auth list # ... docker で gcloud を実行する手順はよく Qiita でも話題になっていますが、完全にホスト上にインストールしたのと同じ使用感のものは見当たらなかったり、サービスアカウントが前提になっているものが多かったので、「以前試したけどうまくいかなかった!」という方はこの方法を試してみていただくといいかもしれませんね。 awscli awscli や AWS CDK のaws や cdk コマンドも同様に Docker 化しておくと、ホストを汚さずにすみ、バージョン管理も楽になったりするのでおすすめです。 aws コマンドの場合は以下のようにしておくと、 aws eks update-kubeconfig コマンドのように $HOME 以下 ($HOME/.kube/config)に書き込むようなコマンドにも対応できます。 $HOME/bin/aws #!/usr/bin/env bash docker run \ -u `id -u`:`id -g` \ -v ~/.aws:/aws \ -v $(pwd):$(pwd) \ --workdir $(pwd) --rm -it \ -e AWS_CONFIG_FILE=/aws/config \ -e AWS_SHARED_CREDENTIALS_FILE=/aws/credentials \ -e AWS_DEFAULT_REGION \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ -e AWS_PROFILE \ -e AWS_ROLE_ARN \ -e AWS_WEB_IDENTITY_TOKEN_FILE \ -e AWS_ROLE_SESSION_NAME \ -e AWS_SESSION_TOKEN \ -e KUBECONFIG=$(pwd)/aws/.kube/config \ amazon/aws-cli:latest \ "$@" おまけ: cdk AWS CDK の cdk コマンドのように、公式にはイメージが配布されていないようなものについては、 docker build のワンライナーを添えておくと、普段どおりコマンドを打つだけでビルドと実行が可能なので便利です。 $HOME/bin/cdk #!/usr/bin/env bash tempdir=$(mktemp -d) || exit 1 cat <<DOCKERFILE | docker build $tempdir -t cdk -f - FROM node:14 RUN npm install -g aws-cdk RUN apt-get update -y && apt-get install less -y RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ unzip awscliv2.zip && \ ./aws/install ENTRYPOINT [ "cdk" ] DOCKERFILE docker run \ -u `id -u`:`id -g` \ -v ~/.aws:/aws \ -v $(pwd):$(pwd) \ --workdir $(pwd) --rm -it \ -e AWS_CONFIG_FILE=/aws/config \ -e AWS_SHARED_CREDENTIALS_FILE=/aws/credentials \ -e AWS_DEFAULT_REGION \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ -e AWS_PROFILE \ -e AWS_ROLE_ARN \ -e AWS_WEB_IDENTITY_TOKEN_FILE \ -e AWS_ROLE_SESSION_NAME \ -e AWS_SESSION_TOKEN cdk "$@" cdk コマンドのようにカレントディレクトリ以下のファイルを読み書きするようなコマンドの場合、 -v $(pwd):$(pwd) や -u オプションでカレントディレクトリ以下にカレントユーザと同じ uid, gid でアクセスできる点が効いてきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacでDockerが遅いのをLimaで解決する(M1/Intel)

MacでDockerが遅いという有名な問題、Limaに乗り換えることでネイティブ程度の速度が出た。 ということでDocker DeskTopを捨てました。 この手順でやると以下の感じになります Mac上で今までどおり docker コマンドや docker-compose を利用できる VSCodeのRemote Containerでも当然利用可能  特にFile IOが大きく改善するので体感的にめっちゃ早くなる Limaに乗り換えの情報は色々あるのだけど、なかなかうまくいかなかったので 自分がやった手順を公開。 現在のdockerを捨てる おそうじ。 以下でコンテナを全部止めて docker stop $(docker ps -aq) docker system prune で全部消せる。イメージが多過ぎたりすると固まる。 Docker Desktopを再起動して、何度か実行すればOK おそうじが終わったらdocker-desktopをアンインストールする Lima セットアップ Limaを使うとUbuntuの仮想マシンをスパッと作ってくれる。 その中にDocker Serverを立てる 参考 インストール brew install lima Lima VMを作成 Limaの設定を default.yaml というファイルで作成する このファイルはLima 公式から持ってこれるが、一部カスタマイズをする default.yaml # Example to use Docker instead of containerd & nerdctl # $ limactl start ./docker.yaml # $ limactl shell docker docker run -it -v $HOME:$HOME --rm alpine # To run `docker` on the host (assumes docker-cli is installed): # $ export DOCKER_HOST=unix://$HOME/docker.sock # $ docker ... # This example requires Lima v0.7.3 or later images: # Hint: run `limactl prune` to invalidate the "current" cache - location: "https://cloud-images.ubuntu.com/impish/current/impish-server-cloudimg-amd64.img" arch: "x86_64" - location: "https://cloud-images.ubuntu.com/impish/current/impish-server-cloudimg-arm64.img" arch: "aarch64" # 重要。Mac側のディレクトリをcontainerにマウントした後、container側から書き込むために設定する # マウントして書き込みを許可するMAC上のパスをセットして、writableをtrueにする。 # 今回は例として、Home DirectoryとHome Directoryのworkディレクトリを指定している mounts: - location: "~" writable: true - location: "/tmp/lima" writable: true - location: "~/work" writable: true ssh: localPort: 60006 # Load ~/.ssh/*.pub in addition to $LIMA_HOME/_config/user.pub , for allowing DOCKER_HOST=ssh:// . # This option is enabled by default. # If you have an insecure key under ~/.ssh, do not use this option. loadDotSSHPubKeys: true # containerd is managed by Docker, not by Lima, so the values are set to false here. containerd: system: false user: false provision: - mode: system script: | #!/bin/bash set -eux -o pipefail command -v docker >/dev/null 2>&1 && exit 0 export DEBIAN_FRONTEND=noninteractive curl -fsSL https://get.docker.com | sh # NOTE: you may remove the lines below, if you prefer to use rootful docker, not rootless systemctl disable --now docker apt-get install -y uidmap dbus-user-session - mode: user script: | #!/bin/bash set -eux -o pipefail systemctl --user start dbus dockerd-rootless-setuptool.sh install docker context use rootless probes: - script: | #!/bin/bash set -eux -o pipefail if ! timeout 30s bash -c "until command -v docker >/dev/null 2>&1; do sleep 3; done"; then echo >&2 "docker is not installed yet" exit 1 fi if ! timeout 30s bash -c "until pgrep rootlesskit; do sleep 3; done"; then echo >&2 "rootlesskit (used by rootless docker) is not running" exit 1 fi hint: See "/var/log/cloud-init-output.log". in the guest # 重要。Mac側のdockerコマンドでLima上のDockerサーバーに接続するため、docker.sockをMac側に晒す portForwards: - guestSocket: "/run/user/{{.UID}}/docker.sock" hostSocket: "{{.Home}}/docker.sock" ポイントは以下2点 mounts の location部分で docker volumeマウントするホスト側のディレクトリを指定して書き込み可能にしておく これをしておかないとコンテナ側でマウントされたファイルの更新ができない。 portForwards で docker.sock をホストマシン(Mac)に露出する これをすることで、Mac上から Lima VM上の docker.sock を直接接続できる。 色々試したけど、docker.sock を晒す方法が一番簡単でうまく動作した。 Lima上にゲストVMを作成する。 limactl start ./default.yaml 起動したらdockerの動作確認 lima docker version mac osにdocker cliをインストール mac上で docker コマンドを使えるように、docker のクライアントだけインストールする 例では#HOME/bin に配置しているけどPATH通っていればどこでもいい # docker cli # Intel Macの場合 curl -OL https://download.docker.com/mac/static/stable/x86_64/docker-20.10.11.tgz # M1 Macの場合 curl -OL https://download.docker.com/mac/static/stable/aarch64/docker-20.10.11.tgz mkdir -p $HOME/bin tar xzvf docker-20.10.11.tgz mv docker/docker $HOME/bin/. chmod 755 $HOME/bin/docker Lima VMのdockerとMacのdockerを接続 ~/.zprofile に以下の記述を追加 Macのdocker cliからLima VMの docker.sock につなぐようにする export DOCKER_HOST=unix://$HOME/docker.sock 設定を反映 source $HOME/.zprofile これでmac上からdockerを操作できる docker version うまくいっていればDocker ServerとしてLima上のDockerにつながる Macにdocker-compose v2をインストール バイナリからインストールする。 Intel Macの場合とM1 Macの場合でバイナリが違う mkdir -p ~/.docker/cli-plugins/ # Intel Mac curl -SL https://github.com/docker/compose/releases/download/v2.1.1/docker-compose-darwin-x86_64 -o ~/.docker/cli-plugins/docker-compose # M1 Mac curl -SL https://github.com/docker/compose/releases/download/v2.1.1/docker-compose-darwin-aarch64 -o ~/.docker/cli-plugins/docker-compose chmod +x ~/.docker/cli-plugins/docker-compose docker compose version これで docker compose up ができる その他 docker compose up で err: exec: "docker-credential-osxkeychain": executable file not found in $PATH これが出た場合、 ~/.docker/config.json を削除することで解消する この構成でやる時の制限 Limaの制限でホスト側の1から1023番ポートまでの特権ポートは利用できない。 例えば、docker run する時のポート指定で 80:80 はダメで 8000:80 とかにする必要がある
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンテナの「生成・起動」と「停止」と「破棄」

[概要] コンテナの「生成/起動/停止/破棄」の、 よく使うコマンドを記載する ※使うコマンドが増えれば、更新しきます [作業] Dockerは起動している前提 [コンテナの生成/起動] docker-composeを使用する前提ですが、 docker-compose.ymlが配置されている層で、以下のコマンドを実行してコンテナを起動 terminal #コンテナを生成・起動する docker-compose up #コンテナを生成・起動する (バックグラウンド) docker-compose up -d コンテナの生成と起動を、実行してくれている [コンテナの停止] 通常通りに、起動したコンテナを以下のコマンドで停止しましょう 「ctrl + c」で抜ける(停止する)ことが可能です もし、上記が不可能!という場合や、バックグラウンドで起動している場合、以下を実行する 1, 新しくターミナルを立ち上げる 2, 以下のコマンドを実行する terminal #実行しているコンテナ一覧を表示するコマンド docker ps 3, 起動されているコンテナIDを確認する terminal CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 60567ff2049d localserver_flask "flask run --host 0.…" 6 hours ago Up 2 seconds 0.0.0.0:8080->8080/tcp localserver_flask_1 4, コンテナIDが確認できたので以下を実行する terminal docker container stop CONTAINER ID 5, 再度、上記2を実行して起動中のコンテナを確認する terminal ONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ※表示されていないので、起動していないことが解る!!!! [補足] 6, ちなみに以下のオプションをつけることで。コンテナが「停止中/起動中」も含めて、全て確認することが確認できる。  ※ここで重要なのは、上記4で実行したのはコンテナを停止しただけで、破棄はされていないこと。 terminal docker ps -a terminal CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 60567ff2049d localserver_flask "flask run --host 0.…" 6 hours ago Exited (0) 5 minutes ago localserver_flask_1 5841d726350e server_flask "flask run --host 0.…" 2 months ago Exited (255) 7 hours ago 0.0.0.0:8080->8080/tcp server_flask_1 [コンテナの破棄] コンテナIDを指定して破棄する場合 以下のコマンドを実行する terminal docker container rm CONTAINER ID コンテナ(停止中)を全て破棄する場合 terminal docker container prune 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerインストール【CentOS8】

Docker インストール 何番煎じでもいい これはメモなんだ、、、 公式手順 気を付ける点 usermod コマンド で 一般ユーザでdockerコマンドを使えるようにする ↑の変更後、いったんログアウトしないと設定反映されない リポジトリセットアップ # sudo dnf -y install yum-utils $ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo インストール $ sudo dnf install docker-ce docker-ce-cli containerd.io 起動 $ sudo systemctl start docker $ sudo systemctl status docker ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled) Active: active (running) since Fri 2021-12-03 06:16:34 UTC; 20s ago Hello-world確認 $ sudo usermod -aG docker $USER →いったんログアウト $ docker run hello-world Hello from Docker! This message shows that your installation appears to be working correctly.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Docker] flask/uwsgi+nginx+postgresを別コンテナで構築する

はじめに 最近Docker上でアプリを動かしてみたいと思い、1~2週間ほど格闘してようやくタイトルにあるような構成でアプリを動かすことに成功しました。ほかの記事でも結構見かけるような構成ではあるのですが、コンテナ起動時のテーブル構築やデータの永続化といったテーマにも触れていきたいとおもいます。 環境 ・Window10 Home 64bit ・WSL2 ・Docker Desktop for Windows ディレクトリ構成 flask/ ├ app/ │ ├ config/ │ │ ├ requirements.txt │ │ └ uwsgi.ini │ ├ Dockerfile │ └ run.py ├ database/ │ ├ data/ │ ├ docker-entrypoint-initdb.d/ │ │ └ 1_create_table.sql │ └ Dockerfile ├ nginx/ │ ├ config/ │ │ └ nginx.conf │ └ Dockerfile └ docker-compose.yml Githubはこちらです。 app まずDockerfileです。 ワークディレクトリは/var/www/appにしていますが、任意です。 アプリケーションに必要なライブラリはrequirements.txtで管理するのでこれをインストールしています。 Dockerfile FROM python:3.9 # コンテナ内の作業ディレクトリを指定(以降のカレントディレクトリ) WORKDIR /var/www/app RUN apt-get update \ && apt-get install -y vim \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* # ホストのカレントディレクトリをコンテナ内のカレントディレクトリにコピー COPY . . # ライブラリインストール RUN pip install -U pip \ && pip install --no-cache-dir -r /var/www/app/config/requirements.txt ライブラリ管理はrequirements.txtで行っています。 requirements.txt uwsgi flask psycopg2 uwsgi.iniはnginxからsocketで通信を受け取る設定にしておきます。 uwsgi.ini [uwsgi] master = True socket = :3031 # http = :3031 chdir = /var/www/app/ wsgi-file = run.py callable = app logto = /var/log/uwsgi.log アプリに関してはHello Worldを返す簡単なものを作っていきます。 run.py from flask import Flask, redirect, url_for, render_template import psycopg2 from psycopg2.extras import DictCursor # from werkzeug.middleware.proxy_fix import ProxyFix app = Flask(__name__) # app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1, x_port=1, x_proto=1) def pg_conn(): setting = { 'host': 'flask_db', # dbコンテナ名を指定 'port': '5432', 'dbname': 'postgres', 'user': 'postgres', 'password': 'postgres' } return psycopg2.connect(**setting) @app.get('/') def index(): return 'Hello World' @app.get('/red') def red(): with pg_conn() as conn: with conn.cursor(cursor_factory=DictCursor) as cur: sql = "INSERT INTO users(name) values('jessy')" cur.execute(sql) conn.commit() return redirect(url_for('index')) if __name__ == "__main__": app.run() database Dockerfileです。イメージを取得するだけなのでdocker-compose.ymlで直接取得しても構わないのですが、あとからDockerfileに定義すべき事項が出てきたときに少しめんどくさいと思ったのでDockerfileを使っています。 Dockerfile FROM postgres:latest 次にコンテナ起動時に必要なテーブルが作成されるように.sqlファイルを作っておきます。コンテナ内の/docker-entrypoint-initdb.dに置いておくとコンテナ起動時に実行してくれます。コンテナの中に入ってデータベースやテーブルを作成するといった作業を省略できます。実行対象ファイルは「.sql」「.sh」「.sql.gz」のようです。また、ファイル名のイニシャルに数字をつけておくことでDockerが順番に実行してくれます。 1_create_table.sql create table if not exists users ( id serial not null , name character varying not null , constraint users_PKC primary key (id) ) ; 最後に/dataという空のディレクトリを作成しておきます。これはこの後出てきますが、データを永続化するために使います。 nginx Dockerfile FROM nginx:latest # vimをインストールしています。必要がなければコメントアウトしてください。 RUN apt-get update \ && apt-get install -y vim \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* 設定 次にnginxの設定ファイルです。おもにnginx-uwsgi間の通信方法についてみていきますので、他の設定については言及しません。今回はTCPソケットとHTTPの2つ通信方法を試してみました。 nginxとuwsgiを同一のコンテナで運用する場合unixソケットで通信することが一般的だと思いますが、今回は別々のコンテナで運用するのでunixソケットは使えません。 TCPソケット 以下はTCPソケットで通信する場合の設定例です。 nginx.conf user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type text/html; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; upstream app_server {     # コンテナ名を使用 server flask_app:3031; } # map $http_x_forwarded_proto $real_scheme { # default $scheme; # https "https"; # } # map $http_host $port { # default $server_port; # "~^[^\:]+:(?<p>\d+)$" $p; # } server { listen 80 default; charset utf-8; server_name _; # proxy_set_header X-Forwarded-For $remote_addr; # proxy_set_header X-Forwarded-Host $host; # proxy_set_header X-Forwarded-Port $port; # proxy_set_header X-Forwarded-Proto $real_scheme; location / { client_max_body_size 1m; client_body_buffer_size 8k; include uwsgi_params; # TCPSocket通信 uwsgi_pass app_server; # HTTP通信 # proxy_pass http://app_server; # proxy_redirect off; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } } TCPソケットを使う場合は、uwsgi_passディレクティブに通信先コンテナとポートを記載します。ここでは通信先コンテナにコンテナ名を利用しています(upstreamディレクティブ)。コンテナ名をコンテナのIPアドレスに変えても動くはずです。 HTTP uwsgiと通信する場合、uwsgi_passディレクティブを使うことが推奨されているので通常はTCPソケットでいいと思うのですが、HTTPで通信することもあるかもしれないのでこちらでも設定してみました。その場合uwsgi_passではなく、proxy_passディレクティブを使うことになります。 以下のように設定を変更します。(httpモジュールのみ抜粋) nginx.conf http { include /etc/nginx/mime.types; default_type text/html; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; upstream app_server {     # コンテナ名を使用 server flask_app:3031; } # 追加 map $http_x_forwarded_proto $real_scheme { default $scheme; https "https"; } # 追加 map $http_host $port { default $server_port; "~^[^\:]+:(?<p>\d+)$" $p; } server { listen 80 default; charset utf-8; server_name _; # 追加 proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $port; proxy_set_header X-Forwarded-Proto $real_scheme; location / { client_max_body_size 1m; client_body_buffer_size 8k; include uwsgi_params; # TCPSocket通信 # uwsgi_pass app_server; # HTTP通信 proxy_pass http://app_server; proxy_redirect off; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } } またuwsgi.iniもHTTPにします。 uwsgi.ini [uwsgi] master = True # socket = :3031 http = :3031 chdir = /var/www/app/ wsgi-file = run.py callable = app logto = /var/log/uwsgi.log さらにアプリ側も設定が必要です。 run.py from flask import Flask, redirect, url_for, render_template import psycopg2 from psycopg2.extras import DictCursor # 追加 from werkzeug.middleware.proxy_fix import ProxyFix app = Flask(__name__) # 追加 app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1, x_port=1, x_proto=1) .... .... いろいろ設定が必要なのはこれらがないとflaskのredirect時にLocationヘッダURLがproxy_passディレクティブに設定されているURL(appコンテナ)になってしまいます。 そうならないようにwerkzeug.middleware.proxy_fixというモジュールを使っています。これはアプリケーションの前にあるプロキシ(ここではnginx)が設定するX-Forwarded-に基づいて、WSGI環境を調整するモジュールです。(ドキュメント) LocationヘッダにはnginxコンテナホストへのURLを設定したいので、X-Forwarded-にはnginxコンテナホストの情報を設定しています(nginx.conf)。 参考:https://github.com/sjmf/reverse-proxy-minimal-example docker-compose.yml ここまでのところがイメージの土台です。 ここからはイメージからコンテナを起動する際の設定について定義になります。 docker-compose.yml version: '3' services: database: container_name: flask_db build: ./database expose: - "5432" volumes: # 1_create_table.sqlをコンテナ内のdocker-entrypoint-initdb.dに置くため - ./database/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d - ./database/data:/var/lib/postgresql/data environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES-DATABASE=postgres - DATABASE_HOST=localhost app: container_name: flask_app build: ./app expose: - "3031"  volumes: - ./app:/var/www/app depends_on: - database command: uwsgi --ini /var/www/app/config/uwsgi.ini nginx: container_name: flask_nginx build: ./nginx restart: always volumes: - ./nginx/config/nginx.conf:/etc/nginx/nginx.conf depends_on: - app ports: - "8080:80" データ永続化 ホストのdatabase/dataをコンテナ内の/var/lib/postgresql/dataにマウントしていますが、これはデータベースの内容が保存される/var/lib/postgresql/dataの内容をホスト上に同期するためです。 したがって、仮にコンテナを削除したとしても新たなコンテナの/var/lib/postgresql/dataにホストのdatabase/dataをマウントさせることで以前のデータを再び同期させ、データを引き継ぐことができます。ちなみに今回のマウント方法をバインドマウントといいます。 Dockerにはもう一つボリュームマウントというマウント方法があります。こちらはバインドマウントのようにホストのディレクトリをマウントさせるのではなく、DockerEngine管理下にあるボリュームと呼ばれる記憶領域をコンテナのディレクトリにマウントするものです。 こちらの方法でもボリュームが存続する限りデータを維持できることになります。ボリュームマウントの方法はトップレベルに明示的にボリュームを作り、バインドマウントのようにコンテナのディレクトリにマウントさせるだけです。 今回の場合であれば、以下のように修正すればよいです。 docker-compose.yml .... services: database: build: ./database container_name: flask_db expose: - "5432" volumes: - ./database/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d # 作成したボリュームをコンテナにマウント - volume_data:/var/lib/postgresql/data environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES-DATABASE=postgres - DATABASE_HOST=localhost ..... ..... # 明示的にvolume_dataというボリューム(ボリューム名は任意)を作成 volumes: volume_data: 実はもう一つ一時ファイルシステムマウントと呼ばれるマウント機能があるようです。今回は試さなかったので書いてませんが興味のある方は調べてみてください。 おわりに これで一応タイトルのような構成でアプリを動かせます。Githubに上げていますのでぜひ参考にしてみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Docker] flask/uWSGI+nginx+postgresを別コンテナで構築する

はじめに 最近Docker上でアプリを動かしてみたいと思い、1~2週間ほど格闘してようやくタイトルにあるような構成でアプリを動かすことに成功しました。ほかの記事でも結構見かけるような構成ではあるのですが、コンテナ起動時のテーブル構築やデータの永続化といったテーマにも触れていきたいとおもいます。 環境 ・Window10 Home 64bit ・WSL2 ・Docker Desktop for Windows ディレクトリ構成 flask/ ├ app/ │ ├ config/ │ │ ├ requirements.txt │ │ └ uwsgi.ini │ ├ Dockerfile │ └ run.py ├ database/ │ ├ data/ │ ├ docker-entrypoint-initdb.d/ │ │ └ 1_create_table.sql │ └ Dockerfile ├ nginx/ │ ├ config/ │ │ └ nginx.conf │ └ Dockerfile └ docker-compose.yml Githubはこちらです。 app まずDockerfileです。 ワークディレクトリは/var/www/appにしていますが、任意です。 アプリケーションに必要なライブラリはrequirements.txtで管理するのでこれをインストールしています。 Dockerfile FROM python:3.9 # コンテナ内の作業ディレクトリを指定(以降のカレントディレクトリ) WORKDIR /var/www/app RUN apt-get update \ && apt-get install -y vim \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* # ホストのカレントディレクトリをコンテナ内のカレントディレクトリにコピー COPY . . # ライブラリインストール RUN pip install -U pip \ && pip install --no-cache-dir -r /var/www/app/config/requirements.txt ライブラリ管理はrequirements.txtで行っています。 requirements.txt uwsgi flask psycopg2 uwsgi.iniはnginxからsocketで通信を受け取る設定にしておきます。 uwsgi.ini [uwsgi] master = True socket = :3031 # http = :3031 chdir = /var/www/app/ wsgi-file = run.py callable = app logto = /var/log/uwsgi.log アプリに関してはHello Worldを返す簡単なものを作っていきます。 run.py from flask import Flask, redirect, url_for, render_template import psycopg2 from psycopg2.extras import DictCursor # from werkzeug.middleware.proxy_fix import ProxyFix app = Flask(__name__) # app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1, x_port=1, x_proto=1) def pg_conn(): setting = { 'host': 'flask_db', # dbコンテナ名を指定 'port': '5432', 'dbname': 'postgres', 'user': 'postgres', 'password': 'postgres' } return psycopg2.connect(**setting) @app.get('/') def index(): return 'Hello World' @app.get('/red') def red(): with pg_conn() as conn: with conn.cursor(cursor_factory=DictCursor) as cur: sql = "INSERT INTO users(name) values('jessy')" cur.execute(sql) conn.commit() return redirect(url_for('index')) if __name__ == "__main__": app.run() database Dockerfileです。イメージを取得するだけなのでdocker-compose.ymlで直接取得しても構わないのですが、あとからDockerfileに定義すべき事項が出てきたときに少しめんどくさいと思ったのでDockerfileを使っています。 Dockerfile FROM postgres:latest 次にコンテナ起動時に必要なテーブルが作成されるように.sqlファイルを作っておきます。コンテナ内の/docker-entrypoint-initdb.dに置いておくとコンテナ起動時に実行してくれます。コンテナの中に入ってデータベースやテーブルを作成するといった作業を省略できます。実行対象ファイルは「.sql」「.sh」「.sql.gz」のようです。また、ファイル名のイニシャルに数字をつけておくことでDockerが順番に実行してくれます。 1_create_table.sql create table if not exists users ( id serial not null , name character varying not null , constraint users_PKC primary key (id) ) ; 最後に/dataという空のディレクトリを作成しておきます。これはこの後出てきますが、データを永続化するために使います。 nginx Dockerfile FROM nginx:latest # vimをインストールしています。必要がなければコメントアウトしてください。 RUN apt-get update \ && apt-get install -y vim \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* 設定 次にnginxの設定ファイルです。おもにnginx-uWSGI間の通信方法についてみていきますので、他の設定については言及しません。今回はuwsgiとHTTPプロトコルの2つを試してみました。 nginxとuWSGIを同一のコンテナで運用する場合unixソケットで通信することが一般的だと思いますが、今回は別々のコンテナで運用するのでunixソケットは使えません。 uwsgi 以下はuwsgiプロトコルで通信する場合の設定例です。 nginx.conf user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type text/html; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; upstream app_server {     # コンテナ名を使用 server flask_app:3031; } # map $http_x_forwarded_proto $real_scheme { # default $scheme; # https "https"; # } # map $http_host $port { # default $server_port; # "~^[^\:]+:(?<p>\d+)$" $p; # } server { listen 80 default; charset utf-8; server_name _; # proxy_set_header X-Forwarded-For $remote_addr; # proxy_set_header X-Forwarded-Host $host; # proxy_set_header X-Forwarded-Port $port; # proxy_set_header X-Forwarded-Proto $real_scheme; location / { client_max_body_size 1m; client_body_buffer_size 8k; include uwsgi_params; # uwsgi(TCP Socketを使用) uwsgi_pass app_server; # HTTP # proxy_pass http://app_server; # proxy_redirect off; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } } uwsgiを使う場合は、uwsgi_passディレクティブに通信先コンテナとポート(TCPソケット)を記載します。ここでは通信先コンテナにコンテナ名を利用しています(upstreamディレクティブ)。コンテナ名をコンテナのIPアドレスに変えても動くはずです。 HTTP uWSGIと通信する場合、uwsgiプロトコル(uwsgi_pass)を使うことが推奨されているのですが、HTTP(proxy_pass)で通信することもあるかもしれないのでこちらでも設定してみました。 以下のように設定を変更します。(httpモジュールのみ抜粋) nginx.conf http { include /etc/nginx/mime.types; default_type text/html; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; upstream app_server {     # コンテナ名を使用 server flask_app:3031; } # 追加 map $http_x_forwarded_proto $real_scheme { default $scheme; https "https"; } # 追加 map $http_host $port { default $server_port; "~^[^\:]+:(?<p>\d+)$" $p; } server { listen 80 default; charset utf-8; server_name _; # 追加 proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $port; proxy_set_header X-Forwarded-Proto $real_scheme; location / { client_max_body_size 1m; client_body_buffer_size 8k; include uwsgi_params; # TCP Socket # uwsgi_pass app_server; # HTTP通信 proxy_pass http://app_server; proxy_redirect off; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } } またuwsgi.iniもHTTPにします。 uwsgi.ini [uwsgi] master = True # socket = :3031 http = :3031 chdir = /var/www/app/ wsgi-file = run.py callable = app logto = /var/log/uwsgi.log さらにアプリ側も設定が必要です。 run.py from flask import Flask, redirect, url_for, render_template import psycopg2 from psycopg2.extras import DictCursor # 追加 from werkzeug.middleware.proxy_fix import ProxyFix app = Flask(__name__) # 追加 app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1, x_port=1, x_proto=1) .... .... いろいろ設定が必要なのはこれらがないとflaskのredirect時にLocationヘッダURLがproxy_passディレクティブに設定されているURL(appコンテナ)になってしまいます。 そうならないようにwerkzeug.middleware.proxy_fixというモジュールを使っています。これはアプリケーションの前にあるプロキシ(ここではnginx)が設定するX-Forwarded-に基づいて、WSGI環境を調整するモジュールです。(ドキュメント) LocationヘッダにはnginxコンテナホストへのURLを設定したいので、X-Forwarded-にはnginxコンテナホストの情報を設定しています(nginx.conf)。 参考:https://github.com/sjmf/reverse-proxy-minimal-example docker-compose.yml ここまでのところがイメージの土台です。 ここからはイメージからコンテナを起動する際の設定について定義になります。 docker-compose.yml version: '3' services: database: container_name: flask_db build: ./database expose: - "5432" volumes: # 1_create_table.sqlをコンテナ内のdocker-entrypoint-initdb.dに置くため - ./database/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d - ./database/data:/var/lib/postgresql/data environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES-DATABASE=postgres - DATABASE_HOST=localhost app: container_name: flask_app build: ./app expose: - "3031"  volumes: - ./app:/var/www/app depends_on: - database command: uwsgi --ini /var/www/app/config/uwsgi.ini nginx: container_name: flask_nginx build: ./nginx restart: always volumes: - ./nginx/config/nginx.conf:/etc/nginx/nginx.conf depends_on: - app ports: - "8080:80" データ永続化 ホストのdatabase/dataをコンテナ内の/var/lib/postgresql/dataにマウントしていますが、これはデータベースの内容が保存される/var/lib/postgresql/dataの内容をホスト上に同期するためです。 したがって、仮にコンテナを削除したとしても新たなコンテナの/var/lib/postgresql/dataにホストのdatabase/dataをマウントさせることで以前のデータを再び同期させ、データを引き継ぐことができます。ちなみに今回のマウント方法をバインドマウントといいます。 Dockerにはもう一つボリュームマウントというマウント方法があります。こちらはバインドマウントのようにホストのディレクトリをマウントさせるのではなく、DockerEngine管理下にあるボリュームと呼ばれる記憶領域をコンテナのディレクトリにマウントするものです。 こちらの方法でもボリュームが存続する限りデータを維持できることになります。ボリュームマウントの方法はトップレベルに明示的にボリュームを作り、バインドマウントのようにコンテナのディレクトリにマウントさせるだけです。 今回の場合であれば、以下のように修正すればよいです。 docker-compose.yml .... services: database: build: ./database container_name: flask_db expose: - "5432" volumes: - ./database/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d # 作成したボリュームをコンテナにマウント - volume_data:/var/lib/postgresql/data environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES-DATABASE=postgres - DATABASE_HOST=localhost ..... ..... # 明示的にvolume_dataというボリューム(ボリューム名は任意)を作成 volumes: volume_data: 実はもう一つ一時ファイルシステムマウントと呼ばれるマウント機能があるようです。今回は試さなかったので書いてませんが興味のある方は調べてみてください。 おわりに これで一応タイトルのような構成でアプリを動かせます。Githubに上げていますのでぜひ参考にしてみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでPython公式イメージ + MeCab + NEologd辞書

MeCabについて mecab-ipadic-neologd  形態素解析がなんぞや?MeCabとはなんぞや?という疑問に関しては他の方のブログでも詳しく解説されていると思いますので、ここでは省きます。  MeCabの辞書ですが、デフォルトのもの以外では、おそらく多くの人が使っているものとしてmecab-ipadic-neologdが挙げられるかと思います。LINE株式会社さんが開発しているOSSで新語・固有表現に強いMeCabの辞書で、今尚頻繁に更新されています。ちなみに2021年の新語・流行語大賞に輝いたリアル二刀流もNEologdにかかればこんな感じ $ mecab -d {デフォルト辞書のパス} リアル二刀流 リアル 名詞,固有名詞,一般,*,*,*,リアル,リアル,リアル 二刀流 名詞,一般,*,*,*,*,二刀流,ニトウリュウ,ニトーリュー $ mecab -d {mecab-ipadic-neologdのパス} リアル二刀流 リアル二刀流 名詞,固有名詞,一般,*,*,*,リアル二刀流,リアルニトウリュウ,リアルニトウリュー 辞書の設定  MeCabでどの辞書を使うのか設定する方法は以下の通りです 明示的に指定する方法 Pythonでは # python import Mecab mecab = MeCab.Tagger(f'-d {使用する辞書のパス}') ターミナルからは # shell $ mecab -d {使用する辞書のパス} 予めデフォルトの辞書を変更しておく方法 # デフォルトの辞書が設定されているmecabrcというファイルを探す $ mecab-config --sysconfdir # 例えば自分のMacでは以下のディレクトリ直下でした /opt/homebrew/etc # 今回使用するPython公式イメージ3.9(Debian GNU/Linux 11)では以下でした /etc # 一先ずmecabrcがどうなっているか確認すると辞書のパスを指定している行を確認できる dicdir = /var/lib/mecab/dic/debian # こんな感じだったり dicdir = /opt/homebrew/lib/mecab/dic/ipadic # こんな感じだったり # 上の定義を新しく mecab-ipadic-neologdのパスにすることでデフォルト辞書が変更される なおインストールしたmecab-ipadic-neologdのパスですが、以下のコマンドで分かリます。(公式レポジトリにも記載されているので参照してください) $ echo `mecab-config --dicdir`"/mecab-ipadic-neologd" Docker環境の構築  Dockerfile、docker-compose.ymlについては Python公式イメージ  DockerHubにあるPythonの公式イメージです。https://hub.docker.com/_/python/  別にUbuntuイメージでもなんでもいいのですが、自分はPython環境を構築するときはPython公式イメージを引っ張ってくることが多いので今回はこちらを使用します。 Dockerfile  Dockerfileで書くべきは以下のような記述です。色々やり方はあると思います。 FROM python:3.9 # mecabの導入 RUN apt-get -y update && \ apt-get -y upgrade && \ apt-get install -y mecab && \ apt-get install -y libmecab-dev && \ apt-get install -y mecab-ipadic-utf8 && \ apt-get install -y git && \ apt-get install -y make && \ apt-get install -y curl && \ apt-get install -y xz-utils && \ apt-get install -y file && \ apt-get install -y sudo # mecab-ipadic-NEologdのインストール RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git && \ cd mecab-ipadic-neologd && \ ./bin/install-mecab-ipadic-neologd -n -y && \ echo dicdir = `mecab-config --dicdir`"/mecab-ipadic-neologd">/etc/mecabrc && \ sudo cp /etc/mecabrc /usr/local/etc && \ cd .. 前半はMeCabの導入、後半でmecab-ipadic-NEologd辞書をインストールしています。また先ほど、 echo `mecab-config --dicdir`"/mecab-ipadic-neologd" でインストールした辞書のディレクトリパスを標準出力できると書きましたが、その結果を/etc/mecabrcにリダイレクトすることでMeCabのデフォルト辞書を再定義しています。丁度下記の部分ですね。 echo dicdir = `mecab-config --dicdir`"/mecab-ipadic-neologd">/etc/mecabrc あと、これは結構ハマりポイントだと思うのですが、以下のようにmecabrcを指定の場所にコピーしてあげ無いと動きません。ターミナルからは使えるんですがpython-mecabで使おうとするとパスが通って無いってエラーが出てしまいます。。。 sudo cp /etc/mecabrc /usr/local/etc && \ 一応、今までの流れ含め GitHub にあげておきました。それにしても早速リアル二刀流が反映されているNEologdは優秀ですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Terraform 及びTerragruntをDocker 上で実行する

なぜDocker上でTerraformを動かしたいのか? 環境やバージョンの違いで動かない問題が起きなくなるので保守性があがる。 Terraformの定義ファイル内でバージョン固定しなくてもよくなる*もちろん固定してもいいが余計なエラーに悩まされることがなくなる。 作るもの Dockerfile docker-compose.yml makefileオプション の3つを用意 Dockerfile FROM python:3.6 ARG pip_installer="https://bootstrap.pypa.io/get-pip.py" ARG AWSCLI_VERSION ARG TERRAFORM_VERSION ARG TERRAGRUNT_VERSION # install aws-cli RUN pip install awscli==${AWSCLI_VERSION} # install command. RUN apt-get update && apt-get install -y less vim wget unzip jq # install terraform RUN wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \ unzip ./terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/local/bin/ \ && rm ./terraform_${TERRAFORM_VERSION}_linux_amd64.zip # install terargrunt RUN wget https://github.com/gruntwork-io/terragrunt/releases/download/v${TERRAGRUNT_VERSION}/terragrunt_linux_amd64 \ && chmod +x ./terragrunt_linux_amd64 \ && mv ./terragrunt_linux_amd64 /usr/local/bin/terragrunt # COPY terraform related files COPY ./terraform /terraform docker-compose.yml docker-compose.yml version: "2" services: terraform: container_name: "terraform" build: context: ../ #Dockerfileのパスを指定する dockerfile: Dockerfile args: AWSCLI_VERSION: "1.16.168" TERRAFORM_VERSION: "1.0.1" TERRAGRUNT_VERSION: "0.31.0" env_file: ./.env working_dir: /terraform tty: true さらにmakefileを用意 .PHONY: build plan apply destroy default:build build: @docker-compose build plan: build @docker-compose run --rm terraform terragrund plan apply: build @docker-compose run --rm terraform terragrunt apply destroy: build @docker-compose run --rm terraform terragrunt destroy これでmake apply とかでTerraformの適用ができるようになります。 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

arista ceos環境構築 忘備録

インストール 環境 Server: 4Core mem 16GB OS: Ubuntu 20.04.1 Docker aptやrpmのデストロパッケージでもいいかもしれないけど念のため最新版インストール。 $ sudo apt-get remove docker docker-engine docker.io containerd runc $ sudo apt-get update $ sudo apt-get install ca-certificates curl gnupg lsb-release $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg $ echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null $ sudo apt-get update $ sudo apt-get install docker-ce docker-ce-cli containerd.io コンテナイメージ取得 Aristaのサイトにユーザー登録し、ログイン(詳細省略) https://www.arista.com/jp/user-registration 下記サイトからcEOSをダウンロード(なぜかtar.xzではなくtarファイルが落ちてきた) https://www.arista.com/jp/support/software-download ここではcEOS64-lab-4.27.0F.tarを利用 ダウンロードしたイメージをDockerにimport $ sudo docker import cEOS64-lab-4.27.0F.tar ceos:4.27.0F # 1分ぐらいかかる $ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE ceos 4.27.0F 831f8479cf54 10 seconds ago 1.73GB ceos起動テスト $ sudo docker create \ --name=ceos \ --privileged \ -e INTFTYPE=eth \ -e ETBA=1 \ -e SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1 \ -e CEOS=1 -e EOS_PLATFORM=ceoslab \ -e container=docker \ -i -t ceos:4.27.0F /sbin/init \ systemd.setenv=INTFTYPE=eth \ systemd.setenv=ETBA=1 \ systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1 \ systemd.setenv=CEOS=1 \ systemd.setenv=EOS_PLATFORM=ceoslab \ systemd.setenv=container=docker # cEOS起動 $ sudo docker start ceos # cEOSコンテナの起動確認 $ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 55ee1850ab59 ceos:4.27.0F "/sbin/init systemd.…" 38 seconds ago Up 23 seconds ceos # ターミナルログインしてみる $ sudo docker exec -it ceos Cli localhost>show version Arista cEOSLab Hardware version: Serial number: Hardware MAC address: Not available System MAC address: Not available Software image version: 4.27.0F-24308433.4270F (engineering build) Architecture: x86_64 Internal build version: 4.27.0F-24308433.4270F Internal build ID: 9088210e-613b-47db-b273-7c7b8d45a086 Image format version: 1.0 cEOS tools version: 1.1 Kernel version: 5.11.0-1022-azure Uptime: 1 minute Total memory: 16395120 kB Free memory: 14754252 kB # cEOSログアウト localhost>quit # cEOSコンテナ停止&削除 $ sudo docker stop ceos ceos $ sudo docker rm ceos ceos docker-topoインストール dockerコマンドでも良いけどnetworkの設定が面倒なので簡単にnodeの作成とnode間リンクを作成できるdocker-topoを利用して下記トポロジーを作成してみる。 $ sudo apt-get install -y python3-pip python-is-python3 $ sudo python3 -m pip install --upgrade --user git+https://github.com/networkop/docker-topo.git # コマンドにパスが通ってないのでシンボリックリンク作成 $ sudo ln -s /root/.local/bin/docker-topo /usr/local/bin/docker-topo # デフォルトで使われるceosイメージがceos:latestなのでタグ作成 $ sudo docker tag ceos:4.27.0F ceos:latest $ sudo docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE ceos 4.27.0F 831f8479cf54 41 minutes ago 1.73GB ceos latest 831f8479cf54 41 minutes ago 1.73GB # 下記設定ファイル作成。 $ cat testnet.yaml VERSION: 2 driver: veth links: - endpoints: ["spine:eth1", "leaf1:eth1"] - endpoints: ["spine:eth2", "leaf2:eth1"] # docker-topo実行 $ sudo docker-topo --create testnet.yaml INFO:__main__: alias spine='docker exec -it testnet_spine Cli' alias leaf1='docker exec -it testnet_leaf1 Cli' alias leaf2='docker exec -it testnet_leaf2 Cli' INFO:__main__:All devices started successfully WARNING:pyroute2.ipdb.main:shutdown in progress # ceosコンテナ起動 $ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7f56757681a9 ceos:latest "/sbin/init systemd.…" 4 minutes ago Up 4 minutes testnet_leaf2 c3ac12303145 ceos:latest "/sbin/init systemd.…" 4 minutes ago Up 4 minutes testnet_leaf1 44d7b1386acc ceos:latest "/sbin/init systemd.…" 4 minutes ago Up 4 minutes testnet_spine # 接続してみる $ sudo docker exec -it testnet_spine Cli localhost> 注意:Config保存 eosのwriteコマンドではConfigが保存されない。 ホストのdocker-topoでrunning configが保存される。次回docker-topo --create testnet.yamlすると保存したconfigで起動する。 # configの保存 $ sudo docker-topo -s testnet.yaml INFO:__main__:All configs saved in ./config # config ディレクトリができる $ ls -a ./config/ . .. testnet_leaf1 testnet_leaf2 testnet_spine # configが保存されている $ cat ./config/testnet_leaf1 > show running ! Command: show running-config at line 1 ! device: localhost (cEOSLab, EOS-4.27.0F-24308433.4270F (engineering build)) ! no aaa root ! transceiver qsfp default-mode 4x10G ! ##### <SNIP > ###### 注意:docker-topo設定ファイル変更 testnet.yamlのような設定ファイルを変更する前に全EOSコンテナを停止する。停止する前にtestnet.yamlを変更してしまうとdocker-topoコマンドから 全EOSコンテナを停止できなくなる。(docker コマンドでゴミ掃除が必要になる) コンテナの停止は下記で。 $ sudo docker-topo --destroy testnet.yaml
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerコンテナにDockerfileの変更が反映されない

はじめに 現在、Web系自社開発企業への転職を目標にポートフォリオを作成中。 Dockerを活用してLEMP環境を構築し、Laravel+VuejsでWebアプリケーションの開発を行っております。 Dockerコンテナのnode.jsのバージョンを変更させるために、Dockerfileを編集した際に Dockerfileの変更がコンテナに反映されないという問題で少しつまづいたので、記録として残します。 目次 やりたいこと やったこと 原因 解説 解決策 参考文献 やりたいこと Dockerfileを編集して、DockerコンテナのNode.jsのバージョンを変更したい やったこと Dockerfileを編集 変更前 Dockerfile # install Node.js COPY --from=node:10.22 /usr/local/bin /usr/local/bin COPY --from=node:10.22 /usr/local/lib /usr/local/lib 変更後 Dockerfile # install Node.js COPY --from=node:16.13 /usr/local/bin /usr/local/bin COPY --from=node:16.13 /usr/local/lib /usr/local/lib コンテナの停止 docker compose stop コンテナの作成・起動 docker compose up -d Node.jsのバージョンを確認 docker compose exec app node -v v10.22.0 Dockerfileの変更が反映されなかった 原因 Dockerfileを変更する前のイメージを参考にしてDockerコンテナを作成、起動したから 解説 DockerコンテナはDockerイメージを参考にして作成される そして、このDockerイメージはDockerfileを参考にして作成されるため Dockerfileの変更をDockerコンテナに反映させるためには、Dockerイメージの作成から実行する必要がある 解決策 buildしてDockerイメージを作成 docker compose build Dockerコンテナを作成、起動 docker compose up -d Node.jsのバージョンを確認 docker compose exec app node -v v16.13.0 無事、Dockerfileの変更がコンテナに反映された 参考文献 Docker利用時のソースコードの変更反映 Dockerfileの変更が反映されない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerで、検証用サーバーを構築してみた⑤ (最終章 実行編)

[概要] Dockerを使用する際のコマンドを、自分なりに理解した内容も踏まえて 記録しておこう。 [コマンド] [現在のディレクトリ構成] LocalServer/ ├── app/ │       └── local_server.py └── Dockerfile └── docker-compose.yml 最終章 これまでの記事で、必要最低限のソフト、ファイルを用意してきました。 この記事では、実際に自身のPCのみでリクエストからレスポンスを一貫して確認してみようと思います。 Dockerの起動 1, Dockerは起動できている? まず、以下のコマンドを実行して、Dockerが起動しているか確認してみよう terminal #実行しているコンテナ一覧を表示するコマンド $ docker ps ※もし、以下のようなエラーが表示されれば、Dockerそのものをが起動出来ていない。 terminal #Dockerを起動出来ていないので、ちゃんと起動してる?と言われる。。。 $ docker ps Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? 以下コマンドで、Dockerを立ち上げよう terminal /usr/bin/open /Applications/Docker.app 以下の内容が、表示されればOK。 terminal #現在はコンテナを起動していないので各項目のみ表示される $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2, コンテナの起動 以下のコマンドを実行して、[docker-compose]から、コンテナを起動してみましょう。 terminal # 停止中のコンテナを対象に、イメージ作成から、コンテナを作成/起動してくれるコマンド $ docker-compose up ※LocalServerフォルダに移動してから実行してください。 3, コンテナ状態の確認 上記1の、コマンドを実行してコンテナが起動されていることを確認してみましょう。 すると今度は以下のような表示になる筈です。 terminal $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 60567ff2049d localserver_flask "flask run --host 0.…" 12 minutes ago Up 7 minutes 0.0.0.0:8080->8080/tcp localserver_flask_1 [各項目の詳細] CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAME コンテナIDの最初の12桁 コンテナ生成の元となったイメージ 起動時のコマンド コンテナ作成後の経過時間 コンテナ起動/停止後の経過時間 バインドしているポート コンテナ名 これで晴れて、自身のPC内に、Dockerを使用→コンテナ起動→検証用サーバーを立ち上げる事が可能となりました。 4, それではブラウザで、「localhost:8080/test」を開いてみましょう。 pythonファイルの記載されている、「リクエストに成功!!」の文言が表示されればOKだが、、、 どうだ、、 どう、、だ? 気持ちの良い瞬間 4, まとめ 今回、初投稿ではありましたが記事①〜⑤を作成してみました。 詳細な情報、必要な情報が抜けていたり、至らない点がいくつもあると思います。 その点は、すみません。(内容に間違いや、アドバイス等ありましたらご指摘いただけますと幸いです) 今後もっともっと、知識を深めて色々な情報をお届けできればなと思ってます。 それと、最後に記事の投稿に迷われている方等、いらっしゃいましたら 私は、以下の内容を感じましたので、少量でも投稿してみる事をオススメします。 ・アウトプットによる、知識の定着 ・記事を公開するので、今までしっかり理解出来ていなかった内容を理解しないといけない。。が  知識量が増える。(結果、楽しいw) ・再度、技術者として自覚し、モチベーションの向上に繋がった ・記事投稿の達成感により、モチベーションの向上に繋がった 今後、この検証用サーバーに対して、スマホアプリから通信した場合や、 Jsonデータを返却したりなど色々と記事にしていこうと思います。 興味がある方は見ていただければと思います。刺激になるので 見てくださった方々、ありがとうございました。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerHubのイメージのタグ一覧を参照する方法

ブラウザでこちらを参照。JSON形式で取得できる。 https://registry.hub.docker.com/v1/repositories/<イメージ名>/tags
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語+Elasticsearchで簡易求人検索バックエンド構築

Go 言語+Elasticsearch で簡易求人サイトバックエンド構築 この記事は、マイナビ Advent Calendar 2021 9 日目の記事です。 今回の記事のソースコードは、GitHubで公開しています。 フォルダ構成は以下のようになります。 . ├── README.md ├── batch │ ├── LoadData.go │ ├── go.mod │ ├── go.sum │ └── test_data.xml ├── docker-compose.yml ├── es │ ├── dic │ │ └── test_dic.csv │ ├── Dockerfile │ ├── script │ │ └── es_init.sh │ └── sudachi │ └── sudachi.json ├── search_api │ ├── Dockerfile │ ├── go.mod │ ├── go.sum │ ├── hr_api │ ├── internal │ │ ├── connect_es.go │ │ ├── hr_query.go │ │ └── hr_search.go │ └── main.go はじめに Go 言語と Elasticsearch で簡易求人サイトバックエンドを構築してみます。 Docker コンテナ上で検索エンジン(Elasticsearch)を動かし、Go 製 Web サーバー(echo)から単語検索クエリを投げます。 単語検索したら、求人が JSON で出力されるような簡易求人検索バックエンドを構築します。 検索する場合は、以下のようなクエリを投げると想定して構築します。 #東京都の"カフェ"の求人を検索する http://localhost:5000/search?keyword=カフェ&state=東京都 #東京都の"Go言語"の求人を検索する http://localhost:5000/search?keyword=Go言語&state=東京都 #神奈川県の"アルバイト・パート"の求人を検索する http://localhost:5000/search?keyword=アルバイト・パート&state=神奈川県 #求人のユニークidから検索する http://localhost:5000/search?id=test keywordは検索ワードを、stateは場所を、idは求人のユニークな番号指定するクエリパラメータとします。 Elasticsearch のダッシュボードとして、Kibana を使用します。 Kibana で、Elasticsearch のデータを確認すると以下のようになります。 また、大量の xml データを json 形式に直して、ElasicSearch に投入するバッチも作成します。 本番の想定だと、一日一回動かして求人データの更新を行います。 今回は、簡単に Go 言語で ElasicSearch に 10 万件程度のデータを BulkInsert していきます。 動作 OS は、Ubuntu 20.04 です。 以下、ツールのバージョンです。 ツール バージョン Go 言語 1.17.2 Docker 1.41 docker-compose 1.29.2 Elasticsearch 7.8.1 Kibana 7.8.1 以下作成するときの手順です。 覚えている範囲で書き下したので、多少前後しますが、参考にしていただければ幸いです。 1. Elasticsearch を構築する まず、Elasic Search を構築します。 Elasticsearch 公式を見てみると、バージョンがいくつかあります。 2021/12/7 時点では、7.14.2が、最新でした。 7.8.1 を選んだ理由はいくつかあります。 最も大きな理由は、Sudachiを Elasticsearch の辞書として使用したいからです。 WorksApplication 様のリポジトリでは、7.4 まで対応されていたので、7.4.1 を採用しました。 辞書にこだわらなければ、他のバージョンでも動作すると思います。 Sudachi を採用した Elasticsearch の Dockerfile は、最終的にこのようになりました。 あまり、Docker コンテナの最適な作成に詳しくはないので参考にならないかもしれません。 コンテナのイメージサイズを減らせるかなぁと試行錯誤していますが、結局あまり変化しなくて悩んでます。 ARG ELASTIC_VER=7.8.1 ARG SUDACHI_PLUGIN_VER=2.0.3 FROM ibmjava:8-jre-alpine as dict_builder ARG ELASTIC_VER ARG SUDACHI_PLUGIN_VER WORKDIR /home RUN wget https://github.com/WorksApplications/Elasticsearch-sudachi/releases/download/v${ELASTIC_VER}-${SUDACHI_PLUGIN_VER}/analysis-sudachi-${ELASTIC_VER}-${SUDACHI_PLUGIN_VER}.zip && \ unzip analysis-sudachi-${ELASTIC_VER}-${SUDACHI_PLUGIN_VER}.zip && \ wget http://sudachi.s3-website-ap-northeast-1.amazonaws.com/sudachidict/sudachi-dictionary-20210802-core.zip && \ unzip sudachi-dictionary-20210802-core.zip && \ mkdir -p /usr/share/Elasticsearch/config/sudachi/ && \ mv sudachi-dictionary-20210802/system_core.dic /usr/share/Elasticsearch/config/sudachi/ && \ rm -rf sudachi-dictionary-20210802-core.zip sudachi-dictionary-20210802/ FROM docker.elastic.co/Elasticsearch/Elasticsearch:${ELASTIC_VER} ARG ELASTIC_VER ARG SUDACHI_PLUGIN_VER COPY es/sudachi/sudachi.json /usr/share/Elasticsearch/config/sudachi/ COPY --from=dict_builder /home/analysis-sudachi-${ELASTIC_VER}-${SUDACHI_PLUGIN_VER}.zip /usr/share/Elasticsearch/ docker-compose.yml の、Elasticsearch 部分を抽出するとこんな感じです。 詳しくは、今回の記事のソースコードGitHubを確認いただけますと幸いです。 Elasticsearch: build: context: . dockerfile: es/dockerfile container_name: Elasticsearch volumes: - es-data:/usr/share/Elasticsearch/data networks: - Elasticsearch ports: - 9200:9200 environment: - discovery.type=single-node - node.name=Elasticsearch - cluster.name=go-Elasticsearch-docker-cluster - bootstrap.memory_lock=true - ES_JAVA_OPTS=-Xms256m -Xmx256m ulimits: { nofile: { soft: 65535, hard: 65535 }, memlock: { soft: -1, hard: -1 } } healthcheck: test: curl --head --max-time 120 --retry 120 --retry-delay 1 --show-error --silent http://localhost:9200 docker-compose.yml で注意したのは、後からバックエンドサーバーから検索できるようにすることであったり、メモリリミット周りです。 後から少し触れますが BulkInsert するときに少し躓きました。 詳しくは公式ドキュメントを参考にしてください。 2. Kibana を構築する Kibana の構築は、公式ドキュメント通りに構築したら動作してくれたので、悩むことがなかったです。 最終的に、docker-compose.yml は以下のようになりました。 kibana: container_name: kibana image: docker.elastic.co/kibana/kibana:7.8.1 depends_on: ["Elasticsearch"] networks: - Elasticsearch ports: - 5601:5601 environment: - Elasticsearch_HOSTS=http://Elasticsearch:9200 - KIBANA_LOGGING_QUIET=true healthcheck: test: curl --max-time 120 --retry 120 --retry-delay 1 --show-error --silent http://localhost:5601 3. Elasticsearch にデータを投入する ここから、Go で Elasticsearch とどのように通信するかで悩みました。 まず、どのパッケージを使用するかで悩みました。 基本的には、以下の 2 つのパッケージの使用が多いと思います。 https://github.com/olivere/elastic https://github.com/elastic/go-Elasticsearch 1 は、スター数が一番多い Go の Elasticsearch クライアントパッケージになります。 非常に使い勝手が良くて、ドキュメントも豊富です。 一番最初はこれで構築しようと考えていました。 2 は Elastic 公式のパッケージなので今回は 2 を使用して作成しようと決めました。 ドキュメントがあまり存在していなくて、GitHub 公式の_exampleを参考にして作成しました。 結構最初は、骨が折れる作業だったのですがなれるとめちゃくちゃ便利な機能がたくさんありました。 慣れるには、時間がかかるパッケージです。 BulkInsert Elasticsearch に今回は、30 万件数の求人データを投入する想定で作成していたので、BulkInsert(go-Elasticsearch では、BulkIndex と呼んでいるらしい)は必須だと考えていました。 まず最初は、普通の Insert で作成しました。 Go 言語のコードを抜粋するとこんな感じです。 req := esapi.IndexRequest{ Index: "baito", DocumentID: string(j.Referencenumber), Body: strings.NewReader(string(jobbody)), Refresh: "true", } 公式の GitHubを参考にして作成したのですが、10 万件程度の挿入に 1 時間 30 分程度かかりました。(計測写真が紛失しました) 大抵の場合、すべての挿入に耐えられず途中で timeout してしまうので、普通の insert で 30 万件は実用的でないと思います。 そこで、BulkInsertを参考にしたのですが、よくわらん...(*´-ω・)ン? (。´-_・)ン? (´・ω・`)モキュ? 結局、このドキュメントの理解(と xml パーサドキュメント)に 3 日を費やしました。 最終的に出来上がった Go 言語のコードは以下のようになりました。 package main import ( "bytes" "encoding/json" "encoding/xml" "flag" "fmt" "io" "log" "math/rand" "os" "strings" "time" "github.com/dustin/go-humanize" "github.com/elastic/go-Elasticsearch/v7" "github.com/elastic/go-Elasticsearch/v7/esapi" "github.com/joho/godotenv" ) type Job struct { Referencenumber string `xml:"referencenumber" json:"referencenumber,string"` Date string `xml:"date" json:"date,string"` Url string `xml:"url" json:"url,string"` Title string `xml:"title" json:"title,string"` Description string `xml:"description" json:"description,string"` State string `xml:"state" json:"state,string"` City string `xml:"city" json:"city,string"` Country string `xml:"country" json:"country,string"` Station string `xml:"station" json:"station,string"` Jobtype string `xml:"jobtype" json:"jobtype,string"` Salary string `xml:"salary" json:"salary,string"` Category string `xml:"category" json:"category,string"` ImageUrls string `xml:"imageUrls" json:"imageurls,string"` Timeshift string `xml:"timeshift" json:"timeshift,string"` Subwayaccess string `xml:"subwayaccess" json:"subwayaccess,string"` Keywords string `xml:"keywords" json:"keywords,string"` } var ( _ = fmt.Print count int batch int ) func init() { flag.IntVar(&count, "count", 300000, "Number of documents to generate") flag.IntVar(&batch, "batch", 1000, "Number of documents to send in one batch") flag.Parse() rand.Seed(time.Now().UnixNano()) } func main() { log.SetFlags(0) type bulkResponse struct { Errors bool `json:"errors"` Items []struct { Index struct { ID string `json:"_id"` Result string `json:"result"` Status int `json:"status"` Error struct { Type string `json:"type"` Reason string `json:"reason"` Cause struct { Type string `json:"type"` Reason string `json:"reason"` } `json:"caused_by"` } `json:"error"` } `json:"index"` } `json:"items"` } var ( buf bytes.Buffer res *esapi.Response err error raw map[string]interface{} blk *bulkResponse jobs []*Job indexName = "baito" numItems int numErrors int numIndexed int numBatches int currBatch int ) log.Printf( "\x1b[1mBulk\x1b[0m: documents [%s] batch size [%s]", humanize.Comma(int64(count)), humanize.Comma(int64(batch))) log.Println(strings.Repeat("▁", 65)) // Create the Elasticsearch client // es, err := Elasticsearch.NewDefaultClient() if err != nil { log.Fatalf("Error creating the client: %s", err) } err = godotenv.Load(".env") if err != nil { log.Fatal("Error loading .env file") } xml_path := os.Getenv("BAITO_XML_PATH") f, err := os.Open(xml_path) if err != nil { log.Fatal(err) } defer f.Close() d := xml.NewDecoder(f) for i := 1; i < count+1; i++ { t, tokenErr := d.Token() if tokenErr != nil { if tokenErr == io.EOF { break } // handle error somehow log.Fatalf("Error decoding token: %s", tokenErr) } switch ty := t.(type) { case xml.StartElement: if ty.Name.Local == "job" { // If this is a start element named "location", parse this element // fully. var job Job if err = d.DecodeElement(&job, &ty); err != nil { log.Fatalf("Error decoding item: %s", err) } else { jobs = append(jobs, &job) } } default: } // fmt.Println("count =", count) } log.Printf("→ Generated %s articles", humanize.Comma(int64(len(jobs)))) fmt.Print("→ Sending batch ") // Re-create the index // if res, err = es.Indices.Delete([]string{indexName}); err != nil { log.Fatalf("Cannot delete index: %s", err) } res, err = es.Indices.Create(indexName) if err != nil { log.Fatalf("Cannot create index: %s", err) } if res.IsError() { log.Fatalf("Cannot create index: %s", res) } if count%batch == 0 { numBatches = (count / batch) } else { numBatches = (count / batch) + 1 } start := time.Now().UTC() // Loop over the collection // for i, a := range jobs { numItems++ currBatch = i / batch if i == count-1 { currBatch++ } // Prepare the metadata payload // meta := []byte(fmt.Sprintf(`{ "index" : { "_id" : "%d" } }%s`, a.Referencenumber, "\n")) // fmt.Printf("%s", meta) // <-- Uncomment to see the payload // Prepare the data payload: encode article to JSON // data, err := json.Marshal(a) if err != nil { log.Fatalf("Cannot encode article %d: %s", a.Referencenumber, err) } // Append newline to the data payload // data = append(data, "\n"...) // <-- Comment out to trigger failure for batch // fmt.Printf("%s", data) // <-- Uncomment to see the payload // // Uncomment next block to trigger indexing errors --> // if a.ID == 11 || a.ID == 101 { // data = []byte(`{"published" : "INCORRECT"}` + "\n") // } // // <-------------------------------------------------- // Append payloads to the buffer (ignoring write errors) // buf.Grow(len(meta) + len(data)) buf.Write(meta) buf.Write(data) // When a threshold is reached, execute the Bulk() request with body from buffer // if i > 0 && i%batch == 0 || i == count-1 { fmt.Printf("[%d/%d] ", currBatch, numBatches) res, err = es.Bulk(bytes.NewReader(buf.Bytes()), es.Bulk.WithIndex(indexName)) if err != nil { log.Fatalf("Failure indexing batch %d: %s", currBatch, err) } // If the whole request failed, print error and mark all documents as failed // if res.IsError() { numErrors += numItems if err := json.NewDecoder(res.Body).Decode(&raw); err != nil { log.Fatalf("Failure to to parse response body: %s", err) } else { log.Printf(" Error: [%d] %s: %s", res.StatusCode, raw["error"].(map[string]interface{})["type"], raw["error"].(map[string]interface{})["reason"], ) } // A successful response might still contain errors for particular documents... // } else { if err := json.NewDecoder(res.Body).Decode(&blk); err != nil { log.Fatalf("Failure to to parse response body: %s", err) } else { for _, d := range blk.Items { // ... so for any HTTP status above 201 ... // if d.Index.Status > 201 { // ... increment the error counter ... // numErrors++ // ... and print the response status and error information ... log.Printf(" Error: [%d]: %s: %s: %s: %s", d.Index.Status, d.Index.Error.Type, d.Index.Error.Reason, d.Index.Error.Cause.Type, d.Index.Error.Cause.Reason, ) } else { // ... otherwise increase the success counter. // numIndexed++ } } } } // Close the response body, to prevent reaching the limit for goroutines or file handles // res.Body.Close() // Reset the buffer and items counter // buf.Reset() numItems = 0 } } // Report the results: number of indexed docs, number of errors, duration, indexing rate // fmt.Print("\n") log.Println(strings.Repeat("▔", 65)) dur := time.Since(start) if numErrors > 0 { log.Fatalf( "Indexed [%s] documents with [%s] errors in %s (%s docs/sec)", humanize.Comma(int64(numIndexed)), humanize.Comma(int64(numErrors)), dur.Truncate(time.Millisecond), humanize.Comma(int64(1000.0/float64(dur/time.Millisecond)*float64(numIndexed))), ) } else { log.Printf( "Sucessfuly indexed [%s] documents in %s (%s docs/sec)", humanize.Comma(int64(numIndexed)), dur.Truncate(time.Millisecond), humanize.Comma(int64(1000.0/float64(dur/time.Millisecond)*float64(numIndexed))), ) } } BulkInsertの公式のサンプルとにらめっこして作成しました。 あと、PC のスペックもそんなに高くない(メモリ 4GB、CPU 2 コア)なので XML パーサも省メモリで行わなければなりませんでした。 ちなみに、この Go 言語のコードを書く前に Python のコードを参考にしていて途中から Go 言語をあきらめようかと思うほどでした。 抜粋するとこんな感じです。 for job in jobs: index = job.as_dict() if job.description == "" or job.description == null: continue bulk_file += json.dumps( {"index": {"_index": index_name, "_type": "_doc", "_id": id}} ) # The optional_document portion of the bulk file bulk_file += "\n" + json.dumps(index) + "\n" if id % 1000 == 0: response = client.bulk(bulk_file) bulk_file = "" id += 1 continue id += 1 if bulk_file != "": response = client.bulk(bulk_file) Go 言語のコードも、Python コードも 1000 件づつ BulkInsert を行うようにしています。 Go 言語のコードだと、14 万件程度のデータを約 3 分で、Elasticsearch に投入することができました。 4. Go バックエンドサーバーを作成する 次に、Go の Web サーバーから Elasticsearch に検索をかける箇所の作成をしました。 (BulkInsert を途中で、あきらめてこっち先にやりました) Go の Web サーバーはいくつかありますが、単純にechoを採用しました。 ドキュメントも豊富で、簡潔に書くことができました。 フォルダ構成は、以下のようになります。 . ├── search_api │ ├── Dockerfile │ ├── go.mod │ ├── go.sum │ ├── hr_api │ ├── internal │ │ ├── connect_es.go │ │ ├── hr_query.go │ │ └── hr_search.go │ └── main.go main.go は、簡潔で echo で Web サーバーを立ちあげるだけです。 package main import ( internal "hr_api/internal" "github.com/labstack/echo" "github.com/labstack/echo/middleware" ) func main() { e := echo.New() e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use(middleware.CORS()) e.GET("/search", internal.HRSearch) e.Logger.Fatal(e.Start(":5000")) } internal 配下は、結構悩んで作成しました。 具体的には構造体をどう持つか(結局、全部 string にしていました;;;)、Elasticsearch との通信をどのように行うか等です。 package internal import ( "bytes" "context" "encoding/json" "fmt" "net/http" "github.com/labstack/echo" ) type Query struct { Keyword string `query:"keyword"` State string `query:"state"` Id string `query:"id"` } type Result struct { Referencenumber string `xml:"referencenumber" json:"referencenumber,string"` Date string `xml:"date" json:"date,string"` Url string `xml:"url" json:"url,string"` Title string `xml:"title" json:"title,string"` Description string `xml:"description" json:"description,string"` State string `xml:"state" json:"state,string"` City string `xml:"city" json:"city,string"` Country string `xml:"country" json:"country,string"` Station string `xml:"station" json:"station,string"` Jobtype string `xml:"jobtype" json:"jobtype,string"` Salary string `xml:"salary" json:"salary,string"` Category string `xml:"category" json:"category,string"` ImageUrls string `xml:"imageUrls" json:"imageurls,string"` Timeshift string `xml:"timeshift" json:"timeshift,string"` Subwayaccess string `xml:"subwayaccess" json:"subwayaccess,string"` Keywords string `xml:"keywords" json:"keywords,string"` } type Response struct { Message string `json:"message"` Results []Result } func HRSearch(c echo.Context) (err error) { // クライアントからのパラメーターを取得 q := new(Query) if err = c.Bind(q); err != nil { return } res := new(Response) var ( b map[string]interface{} buf bytes.Buffer ) // Elasticsearch へのクエリを作成 query := CreateQuery(q) json.NewEncoder(&buf).Encode(query) fmt.Printf(buf.String()) // Elasticsearch へ接続 es, err := ConnectElasticsearch() if err != nil { c.Error(err) } // Elasticsearch へクエリ r, err := es.Search( es.Search.WithContext(context.Background()), es.Search.WithIndex("baito"), es.Search.WithBody(&buf), es.Search.WithTrackTotalHits(true), es.Search.WithPretty(), ) if err != nil { c.Error(err) } defer r.Body.Close() if err := json.NewDecoder(r.Body).Decode(&b); err != nil { c.Error(err) } // クエリの結果を Responce.Results に格納 for _, hit := range b["hits"].(map[string]interface{})["hits"].([]interface{}) { result := new(Result) doc := hit.(map[string]interface{}) fmt.Printf(result.Title) result.Referencenumber = doc["_source"].(map[string]interface{})["referencenumber"].(string) result.Date = doc["_source"].(map[string]interface{})["date"].(string) result.Url = doc["_source"].(map[string]interface{})["url"].(string) result.Title = doc["_source"].(map[string]interface{})["title"].(string) result.State = doc["_source"].(map[string]interface{})["state"].(string) result.Category = doc["_source"].(map[string]interface{})["category"].(string) result.Description = doc["_source"].(map[string]interface{})["description"].(string) result.City = doc["_source"].(map[string]interface{})["city"].(string) result.Country = doc["_source"].(map[string]interface{})["country"].(string) result.Station = doc["_source"].(map[string]interface{})["station"].(string) result.Jobtype = doc["_source"].(map[string]interface{})["jobtype"].(string) result.Salary = doc["_source"].(map[string]interface{})["salary"].(string) result.ImageUrls = doc["_source"].(map[string]interface{})["imageurls"].(string) result.Timeshift = doc["_source"].(map[string]interface{})["timeshift"].(string) result.Subwayaccess = doc["_source"].(map[string]interface{})["subwayaccess"].(string) result.Keywords = doc["_source"].(map[string]interface{})["keywords"].(string) res.Results = append(res.Results, *result) } res.Message = "検索に成功しました。" return c.JSON(http.StatusOK, res) } hr_query.go は、本当であれば一番考えなければいけない箇所だと思います。 検索に重みづけをして、UX を大幅に改善できる箇所だと思います。 package internal func CreateQuery(q *Query) map[string]interface{} { query := map[string]interface{}{} if q.Id != "" { query = map[string]interface{}{ "query": map[string]interface{}{ "bool": map[string]interface{}{ "must": []map[string]interface{}{ { "match": map[string]interface{}{ "referencenumber": q.Id, }, }, }, }, }, } } else if q.Keyword != "" && q.State != "" { query = map[string]interface{}{ "query": map[string]interface{}{ "bool": map[string]interface{}{ "must": []map[string]interface{}{ { "bool": map[string]interface{}{ "should": []map[string]interface{}{ { "match": map[string]interface{}{ "title": map[string]interface{}{ "query": q.Keyword, "boost": 3, }, }, }, { "match": map[string]interface{}{ "description": map[string]interface{}{ "query": q.Keyword, "boost": 2, }, }, }, { "match": map[string]interface{}{ "category": map[string]interface{}{ "query": q.Keyword, "boost": 1, }, }, }, }, "minimum_should_match": 1, }, }, { "bool": map[string]interface{}{ "must": []map[string]interface{}{ { "match": map[string]interface{}{ "state": q.State, }, }, }, }, }, }, }, }, } } else if q.Keyword != "" && q.State == "" { query = map[string]interface{}{ "query": map[string]interface{}{ "bool": map[string]interface{}{ "should": []map[string]interface{}{ { "match": map[string]interface{}{ "title": map[string]interface{}{ "query": q.Keyword, "boost": 3, }, }, }, { "match": map[string]interface{}{ "description": map[string]interface{}{ "query": q.Keyword, "boost": 2, }, }, }, { "match": map[string]interface{}{ "category": map[string]interface{}{ "query": q.Keyword, "boost": 1, }, }, }, }, "minimum_should_match": 1, }, }, } } else if q.Keyword == "" && q.State != "" { query = map[string]interface{}{ "query": map[string]interface{}{ "bool": map[string]interface{}{ "must": []map[string]interface{}{ { "match": map[string]interface{}{ "state": q.State, }, }, }, }, }, } } return query } Elasticsearch に接続して通信する部分です。ここは、Qiita 等に上がっている記事を参考にさせていただいたと思います,,,が記事の URL が見つからなくなってしまいました。。。 package internal import ( "os" Elasticsearch "github.com/elastic/go-Elasticsearch/v7" ) func ConnectElasticsearch() (*Elasticsearch.Client, error) { // 環境変数 ES_ADDRESS がある場合は記述されているアドレスに接続 // ない場合は、 http://localhost:9200 に接続 var addr string if os.Getenv("ES_ADDRESS") != "" { addr = os.Getenv("ES_ADDRESS") } else { addr = "http://localhost:9200" } cfg := Elasticsearch.Config{ Addresses: []string{ addr, }, } es, err := Elasticsearch.NewClient(cfg) return es, err } 5. ブラウザで確認してみる これで、やっと動くようになります。 docker-compose up を行って、go run main.goとすると、ブラウザから確認できると思います。 VSCode で、行うとわかりやすいと思います。 Remote SSH で開発環境用のサーバーで行っている場合は、上司の note 等参考にしていただけますと嬉しです。 こんな感じで、ブラウザから確認できます。 http://localhost:5000/search?keyword=カフェ&state=東京都 { "message": "検索に成功しました。", "Results": [ { "referencenumber": "test", "date": "2222-11-01", "url": "test", "title": "おしゃれカフェ・店舗スタッフ/ブック&カフェ/アルバイト・パート/おしゃれカフェ", "description": "【省略】", "state": "東京都", "city": "渋谷区", "country": "日本", "station": "山手線渋谷駅 徒歩700分", "jobtype": "アルバイト・パート", "salary": "test円", "category": "飲食・フード×おしゃれカフェ", "imageurls": "test", "timeshift": "週3日以上/1日3時間以上", "subwayaccess": "山手線渋谷駅徒歩700分", "keywords": "test" }, 6. 最後に マイナビでは、求人サイトを多く運営しているます。 Go 言語で簡易的求人サイトを作成することで、技術的な背景を再勉強することができました。 興味がある方は、是非作成してみてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerを支えるLinuxのコンテナ技術

はじめに こんにちは、最近パラパラチャーハンが作れるようになりました。コツは「ご飯の量と同じくらいみじん切りにした野菜を入れて誤魔化す」です。 はい、ということで今回はDockerはどうやって作られているのか気になって調べたことをまとめていきたいと思います。実際の細かい動きはどうしても図とかないと分かりにくいと思うので、その辺は参考になった記事を紹介していきます。 前提知識 Dockerは Linux が提供するコンテナの技術を使用して実現しているため、前提知識としてLinuxの基本的な知識が必要です。今回はその説明は省きますが、以下の本がすごく分かりやすかったので是非読んでみてください。忙しい人は、自分がこの本を読んでまとめていたものがあったのでもし興味があればそちらをご覧ください。 最初に読むといい感じの本 個人的にまとめたもの 余談ですが次に読むといい感じの本も紹介しておきます。Linuxについて調べれば調べるほどC言語と仲良くなれると思うので、C言語に苦手意識のある方はLinuxから入るといいかもしれないです。(C言語が書けるようになる訳ではないですが、確実にC言語に対するイメージが変わると思います。) 次に読むといい感じの本 コンテナの実態 コンテナは名前からしてもよく箱のようなイメージを持つことが多いですが、実際は任意の範囲でプロセスやNamespaceなどを区切って、その区切った範囲でどのくらいのリソースを利用できるのか設定しています。コンピュータ内で論理的(仮想的)に環境を区切って(隔離して)おくことで例えば以下のようなことができます。 自分のパソコンの環境を汚さなくて済む 簡単に環境を構築したり、破棄したりできる 上記で隔離という用語が出てきましたが、具体的に何を隔離していて、何を設定しているのか定番のやつを紹介します。 補足)自分のPCの環境をホストと呼び、PCの環境を論理的に区切った範囲内のことをコンテナと呼ぶようにします。あと、隔離=「(論理的に)区切る」と読み変えると分かりやすいかもしれないです。 UTS名前空間 ホスト名、ドメイン名。これを隔離することでホストから見たホスト名とコンテナから見た方ホスト名を別のものにすることができます。言い方を変えるとホスト側とコンテナ側のホスト名を別々に設定できます。 PID名前空間 PIDとはプロセスIDのことです。通常、プロセスIDはプロセスが生成されるごとにカーネルにより一意の数値が割り当てられますが、隔離をすることでホストとコンテナそれぞれでそれぞれのPIDを割り当てることができます。そのため、ホストとコンテナで重複する値を設定することができたりします。例えば、コンテナのあるプロセスのPIDが3でもホストから見るとそのプロセスのPIDは8に設定されているみたいなことになっています。 ネットワークインターフェース 基本的にコンテナごとに仮想的なネットワークインターフェースを付与しておきます。ネットワークインターフェースとはインターネットの入り口で、有線LANポートとかWi-Fiが入ってくる入り口をイメージするといいと思います。このインターフェースによってコンテナでパケットを受信(ネット接続)することができます。 コンピュータのリソース プロセスなどを隔離したら、その隔離した範囲でどのくらいのリソース(メモリ、CPU)を使えるのか設定をします。コンテナ1ではメモリを1GB使えて、コンテナ2ではメモリを2GB使えるみたいなことができます。 ファイルシステムの分離 基本的に一つのコンテナにつき一つのルートディレクトリを持っています。docker exec -it コンテナ名 bashとかしてdockerでコンテナに入ったときに、シェルのプロンプト変わりますよね。あれです。あれはルートが変わっていて、シェルの設定ファイルがないためにプロンプトに色とかがついていないです。コンテ内でpwdコマンドを実行してみてもホストとは違う結果が得られるのもルートが変わっているからです。でも、Dockerファイルで指定した作業ディレクトリとかアプリケーションの動作に必要なファイル(nodejsとか)とかはありますよね。ルートを変えることでルートディレクトリの構造を必要最低限ごっそり持ってきてそこに必要なファイルのみを置いていくことができます。また、コンテナを破棄するときもそのコンテナのルートディレクトリを削除するだけで綺麗さっぱり、コンテナ内のアプリの動作環境に必要なファイルたちを消すことができます。 Linuxのコンテナ技術 コンテナの実態がなんとなく掴めたところで、次はそれを実際にどうやって実現しているのか気になってくると思います。答えは、Linuxから提供される機能を使ってコンピュータ内で環境を区切っています。 NameSpace: UTS、PID、ネットワークなどの名前空間を分離します。 ControlGroupe: リソースを設定します。 FileSystem: 必要なファイルをマウントします。 Chroot: ルートディレクトリを変更します。 具体的なLinuxの機能の説明はこちらの記事をご覧ください。(丸投げ笑) 実際にどうやって実装してくのか気になって調べたら以下の記事たちを見つけました。 Containers From Scratch • Liz Rice • GOTO 2018 ライブコーディングでGoによりdockerを作っています。これを見るとなんとなくコンテナの実装イメージができると思います。この動画の日本語の解説がありました↓ 【Go言語】自作コンテナ沼。スクラッチでミニDockerを作ろう - カミナシ開発者ブログ docker内でLinux環境を作り、そこで自作Dockerを体験することができます。 Fewbytes/rubber-docker pythonでdockerを作るワークショップらしいいです。難しめ。がっつり作っていく感じ。 OverlayFS OverlayFSというLinuxが提供してくれるファイルシステムをdockerでは使っているみたいです。このファイルシステムを使うことでコンテナのファイルを効率よく管理できます。OverlayFSは何者でdockerでどうやって使われているかは以下の記事がわかりやすかったです。タイトル通り世界一わかりみが深いと思います。 【連載】世界一わかりみが深いコンテナ & Docker入門 〜 その6:Dockerのファイルシステムってどうなってるの? 〜 | SIOS Tech. Lab Network 以下の記事ではdockerコンテナがどうやってネット接続しているのか解説してくれています。仮想スイッチとか仮想NICとかはLinuxの機能で用意していることになります。僕たちが Docker for Macをインストールしたときには自動で物理NICから仮想スイッチまでの経路は用意してくれているみたいですね。またdockerでコンテナを作った時はそのコンテナにくっつける仮想NICを生成して仮想スイッチに接続するみたいなことを透過的にやってくれてるみたいです。 【連載】世界一わかりみが深いコンテナ & Docker入門 〜 その5:Dockerのネットワークってどうなってるの? 〜 | SIOS Tech. Lab 最後に 今回自分は 「docker自作したい => Linuxを調べる => C言語いいかもしれない」 みたいな感じで今まで苦手意識のあったC言語に興味を持つことができました。自分があまり興味を持てない部分は自分の知識不足で発生するパターンがあるんだなと思いました。興味を持てる範囲から少しずつ知識を増やしていこうと思います!最後に書くことが思いつかなくて大学の実験レポートくらいの真面目度になってしまいましたが、最後まで読んでいただきありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel Docker AWS EC2デプロイ②

前回のEC2デプロイ①に続き、今回はRDSの作成についてご紹介していきます! RDSを作成 1.サービス画面から「RDS」で検索し、RDSコンソール画面にアクセス。 2.左メニューバーの「データベース」にアクセス→「データベースを作成」で各項目入力。 ※注意点は、バージョンをポートフォリオのバージョンに合わせることと、テンプレートを「無料利用枠」にすること。 3.「接続」までスクロール→赤線枠の箇所を入力。 4.「追加設定」までスクロール→「最初のデータベース名」を入力→一番下までスクロールして「データベースを作成」 5.数分したらDBが作成完了→作成したDB(database-1)をクリック 6.作成された「VPCセキュリティグループ」をクリック。 7.「セキュリティグループ」をクリック→「インバウンドのルールを編集」をクリック 8.EC2インスタンス作成時に設定したセキュリティグループを選択。 すると、赤字でエラーメッセージが出ます。 その対処として、一度ルールを削除してから再度ルールを追加します。 接続 1.ターミナルからインスタンスに接続。 .ssh $ ssh -i "sample-key.pem" ec2-user@ec2-52-68-186-250.ap-northeast-1.compute.amazonaws.com Last login: Thu Dec 2 13:52:30 2021 from p3014131-ipoe.ipoe.ocn.ne.jp __| __|_ ) _| ( / Amazon Linux 2 AMI ___|\___|___| https://aws.amazon.com/amazon-linux-2/ 2.パッケージのアップデート [ec2-user@ip-172-31-45-44 ~]$ sudo yum update -y 読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd amzn2-core | 3.7 kB 00:00:00 No packages marked for update 3.MySQL、Git、Apache、cURLをインストール [ec2-user@ip-172-31-45-44 ~]$ sudo yum -y install mysql git httpd curl ・・・ #省力 完了しました! 4.nginx起動 [ec2-user@ip-172-31-45-44 ~]$ sudo systemctl start nginx.service #起動 [ec2-user@ip-172-31-45-44 ~]$ sudo systemctl status nginx.service #起動確認 ● nginx.service - The nginx HTTP and reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled) Active: active (running) since 木 2021-12-02 12:38:54 UTC; 5h 22min ago Main PID: 1217 (nginx) CGroup: /system.slice/nginx.service ├─1217 nginx: master process /usr/sbin/nginx └─1218 nginx: worker process 12月 02 12:38:54 ip-172-31-45-44.ap-northeast-1.compute.internal systemd[1]: St... 12月 02 12:38:54 ip-172-31-45-44.ap-northeast-1.compute.internal nginx[1211]: n... 12月 02 12:38:54 ip-172-31-45-44.ap-northeast-1.compute.internal nginx[1211]: n... 12月 02 12:38:54 ip-172-31-45-44.ap-northeast-1.compute.internal systemd[1]: St... Hint: Some lines were ellipsized, use -l to show in full. 5.MySQLに接続 #mysqlログインコマンド $ mysql -h database-1.csqevja7nem5.ap-northeast-1.rds.amazonaws.com -P 3306 -u admin -p 「database-1.csqevja7nem5.ap-northeast-1.rds.amazonaws.com」の部分は、RDSコンソールで作成したDBをクリックすれば、エンドポイントから確認できます。 また、ログインコマンド実行後にパスワードの入力を求められますが、そちらは自身で設定したマスターパスワードになります。 ※もしマスターパスワードを忘れた場合はこちらの手順で変更できます [ec2-user@ip-172-31-45-44 ~]$ mysql -h database-1.csqevja7nem5.ap-northeast-1.rds.amazonaws.com -P 3306 -u admin -p Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 24 Server version: 8.0.23 Source distribution Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MySQL [(none)]> show databases; #DB確認 +--------------------+ | Database | +--------------------+ | information_schema | | laravel_zaiko | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.00 sec) show databases;で作成したDBを確認し、作成したDB(laravel_zaiko)が存在してれば問題ありません! 続く とりあえずここまででDBの作成と接続は完了です! 次回はLaravelの起動を行っていきます! 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む