MySQL コンテナ作成時にテーブルとプロシージャを作る

  1. 必要なファイル
  2. init.sql の内容
  3. .env の内容
  4. Dockerfile の内容
  5. コンテナの作成
  6. MySQL で確認

1. 必要なファイル

とりあえず必要なファイルは三種類。init.sql、.env、そして Dockerfile です。

3つとも同じディレクトリに配置します。

ls -a
.                       ..			.env                    Dockerfile              init.sql

2. init.sql の内容

--init.sql

CREATE DATABASE IF NOT EXISTS buzzing;
USE buzzing;

CREATE TABLE IF NOT EXISTS `yt_mst_cnl` (
  `channel_id` varchar(40) NOT NULL,
  `channel_name` tinytext,
  `description` text,
  `thumbnail` text,
  `uploads_list` varchar(40) DEFAULT NULL,
  `published_at` date DEFAULT NULL,
  `data_update_date` date DEFAULT NULL,
  PRIMARY KEY (`channel_id`),
  KEY `idx_published_at` (`published_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE IF NOT EXISTS `yt_mst_vid` (
  `video_id` varchar(20) NOT NULL,
  `video_name` text,
  `description` text,
  `thumbnail` text,
  `channel_id` varchar(40) DEFAULT NULL,
  `published_at` varchar(8) DEFAULT NULL,
  `data_update_date` varchar(8) DEFAULT NULL,
  PRIMARY KEY (`video_id`),
  KEY `fk_channel` (`channel_id`),
  KEY `idx_published_at` (`published_at`),
  CONSTRAINT `fk_channel` FOREIGN KEY (`channel_id`) REFERENCES `yt_mst_cnl` (`channel_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE IF NOT EXISTS `yt_pfm_cnl` (
  `channel_id` varchar(40) DEFAULT NULL,
  `subscriber_count` bigint DEFAULT NULL,
  `hidden_subscriber_count` varchar(1) DEFAULT NULL,
  `view_count` bigint DEFAULT NULL,
  `video_count` int DEFAULT NULL,
  `data_date` varchar(8) DEFAULT NULL,
  KEY `fk_channel_pfm` (`channel_id`),
  CONSTRAINT `fk_channel_pfm` FOREIGN KEY (`channel_id`) REFERENCES `yt_mst_cnl` (`channel_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE IF NOT EXISTS `yt_pfm_vid` (
  `video_id` varchar(20) DEFAULT NULL,
  `view_count` bigint DEFAULT NULL,
  `like_count` int DEFAULT NULL,
  `dislike_count` int DEFAULT NULL,
  `favorite_count` int DEFAULT NULL,
  `comment_count` int DEFAULT NULL,
  `most_used_words` text,
  `data_date` varchar(8) DEFAULT NULL,
  KEY `fk_video_pfm` (`video_id`),
  CONSTRAINT `fk_video_pfm` FOREIGN KEY (`video_id`) REFERENCES `yt_mst_vid` (`video_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE IF NOT EXISTS `yt_analysis_07` (
  `channel_id` varchar(40) DEFAULT NULL,
  `channel_name` tinytext,
  `view_count` bigint DEFAULT NULL,
  `like_count` int DEFAULT NULL,
  `dislike_count` int DEFAULT NULL,
  `favorite_count` int DEFAULT NULL,
  `comment_count` int DEFAULT NULL,
  `video_count` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

DROP PROCEDURE IF EXISTS `buzzing`.`yt_analysis_07`;
DELIMITER //
CREATE PROCEDURE `buzzing`.`yt_analysis_07`(IN published_after VARCHAR(8))
BEGIN
    -- truncate the yt_analysis_07 table
    TRUNCATE TABLE yt_analysis_07;

    -- insert data into the yt_analysis_07 table
    INSERT INTO yt_analysis_07
    SELECT 
        channel_id,
        channel_name,
        view_count,
        like_count,
        dislike_count,
        favorite_count,
        comment_count,
        video_count
    FROM (
        SELECT 
            B.channel_id AS channel_id, 
            MAX(C.channel_name) AS channel_name, 
            SUM(A.view_count) AS view_count, 
            SUM(A.like_count) AS like_count, 
            SUM(A.dislike_count) AS dislike_count, 
            SUM(A.favorite_count) AS favorite_count, 
            SUM(A.comment_count) AS comment_count, 
            COUNT(*) AS video_count
        FROM yt_pfm_vid A
        LEFT JOIN yt_mst_vid B ON A.video_id = B.video_id
        LEFT JOIN (
            SELECT channel_id, MAX(channel_name) AS channel_name FROM yt_mst_cnl GROUP BY channel_id
        ) C ON B.channel_id = C.channel_id
        WHERE B.published_at >= @published_after
        GROUP BY channel_id
    ) T1
    ORDER BY view_count DESC;
    COMMIT;
END //
DELIMITER ;

3. .env の内容

MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_USER=admin
MYSQL_PASSWORD=password
MYSQL_DATABASE=buzzing

4. Dockerfile の内容

# Dockerfile

FROM mysql

ADD init.sql /docker-entrypoint-initdb.d

5. コンテナの作成

% docker build -t docker_mysql:1.0 .
% docker run --env-file .env --name docker_mysql -p 13306:3306 -it -d docker_mysql:1.0
% docker exec -it docker_mysql bash                               
bash-4.4# 

6. MySQL で確認

bash-4.4# mysql -u admin -p
Enter password:
mysql> 

テーブルの確認

mysql> use buzzing;
mysql> show tables;
+-------------------+
| Tables_in_buzzing |
+-------------------+
| yt_analysis_07    |
| yt_mst_cnl        |
| yt_mst_vid        |
| yt_pfm_cnl        |
| yt_pfm_vid        |
+-------------------+
5 rows in set (0.00 sec)

プロシージャの確認

mysql> call buzzing.yt_analysis_07('20230101');
Query OK, 0 rows affected (0.03 sec)

ちなみに DBeaver でもポート 13306 を通して接続できました。

Apache Airflow を MySQL で Docker Compose してみた

Apache Airflow 公式サイトから取得できいる docker-compose.yaml はメタデータ用のデータベースに posgresql を使っているのですが、MySQL で立ち上げられる様に変更してみました。

これはその時の備忘録です。

  1. 公式サイトから YAML ファイルを取得
  2. 不要な部分をコメントアウト
  3. Postgresql の部分を MySQL に変更
  4. コメント部分を削除

1. 公式サイトから YAML ファイルを取得

公式サイトのリンクから YAML ファイルを取得

% mkdir docker_airflow
% cd docker_airflow
% mkdir -p ./dags ./logs ./plugins ./config

% curl -LfO 'https://airflow.apache.org/docs/apache-airflow/2.6.1/docker-compose.yaml'

中身がこれ(2023 年 5 月時点)

version: '3.8'
x-airflow-common:
  &airflow-common
  # In order to add custom dependencies or upgrade provider packages you can use your extended image.
  # Comment the image line, place your Dockerfile in the directory where you placed the docker-compose.yaml
  # and uncomment the "build" line below, Then run `docker-compose build` to build the images.
  image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:2.6.1}
  # build: .
  environment:
    &airflow-common-env
    AIRFLOW__CORE__EXECUTOR: CeleryExecutor
    AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    # For backward compatibility, with Airflow <2.3
    AIRFLOW__CORE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@postgres/airflow
    AIRFLOW__CELERY__BROKER_URL: redis://:@redis:6379/0
    AIRFLOW__CORE__FERNET_KEY: ''
    AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true'
    AIRFLOW__CORE__LOAD_EXAMPLES: 'true'
    AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth,airflow.api.auth.backend.session'
    # yamllint disable rule:line-length
    # Use simple http server on scheduler for health checks
    # See https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/check-health.html#scheduler-health-check-server
    # yamllint enable rule:line-length
    AIRFLOW__SCHEDULER__ENABLE_HEALTH_CHECK: 'true'
    # WARNING: Use _PIP_ADDITIONAL_REQUIREMENTS option ONLY for a quick checks
    # for other purpose (development, test and especially production usage) build/extend Airflow image.
    _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:-}
  volumes:
    - ${AIRFLOW_PROJ_DIR:-.}/dags:/opt/airflow/dags
    - ${AIRFLOW_PROJ_DIR:-.}/logs:/opt/airflow/logs
    - ${AIRFLOW_PROJ_DIR:-.}/config:/opt/airflow/config
    - ${AIRFLOW_PROJ_DIR:-.}/plugins:/opt/airflow/plugins
  user: "${AIRFLOW_UID:-50000}:0"
  depends_on:
    &airflow-common-depends-on
    redis:
      condition: service_healthy
    postgres:
      condition: service_healthy

services:
  postgres:
    image: postgres:13
    environment:
      POSTGRES_USER: airflow
      POSTGRES_PASSWORD: airflow
      POSTGRES_DB: airflow
    volumes:
      - postgres-db-volume:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "airflow"]
      interval: 10s
      retries: 5
      start_period: 5s
    restart: always

  redis:
    image: redis:latest
    expose:
      - 6379
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 30s
      retries: 50
      start_period: 30s
    restart: always

  airflow-webserver:
    <<: *airflow-common
    command: webserver
    ports:
      - "8080:8080"
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-scheduler:
    <<: *airflow-common
    command: scheduler
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8974/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-worker:
    <<: *airflow-common
    command: celery worker
    healthcheck:
      test:
        - "CMD-SHELL"
        - 'celery --app airflow.executors.celery_executor.app inspect ping -d "celery@$${HOSTNAME}"'
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    environment:
      <<: *airflow-common-env
      # Required to handle warm shutdown of the celery workers properly
      # See https://airflow.apache.org/docs/docker-stack/entrypoint.html#signal-propagation
      DUMB_INIT_SETSID: "0"
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-triggerer:
    <<: *airflow-common
    command: triggerer
    healthcheck:
      test: ["CMD-SHELL", 'airflow jobs check --job-type TriggererJob --hostname "$${HOSTNAME}"']
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-init:
    <<: *airflow-common
    entrypoint: /bin/bash
    # yamllint disable rule:line-length
    command:
      - -c
      - |
        function ver() {
          printf "%04d%04d%04d%04d" $${1//./ }
        }
        airflow_version=$$(AIRFLOW__LOGGING__LOGGING_LEVEL=INFO && gosu airflow airflow version)
        airflow_version_comparable=$$(ver $${airflow_version})
        min_airflow_version=2.2.0
        min_airflow_version_comparable=$$(ver $${min_airflow_version})
        if (( airflow_version_comparable < min_airflow_version_comparable )); then
          echo
          echo -e "\033[1;31mERROR!!!: Too old Airflow version $${airflow_version}!\e[0m"
          echo "The minimum Airflow version supported: $${min_airflow_version}. Only use this or higher!"
          echo
          exit 1
        fi
        if [[ -z "${AIRFLOW_UID}" ]]; then
          echo
          echo -e "\033[1;33mWARNING!!!: AIRFLOW_UID not set!\e[0m"
          echo "If you are on Linux, you SHOULD follow the instructions below to set "
          echo "AIRFLOW_UID environment variable, otherwise files will be owned by root."
          echo "For other operating systems you can get rid of the warning with manually created .env file:"
          echo "    See: https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#setting-the-right-airflow-user"
          echo
        fi
        one_meg=1048576
        mem_available=$$(($$(getconf _PHYS_PAGES) * $$(getconf PAGE_SIZE) / one_meg))
        cpus_available=$$(grep -cE 'cpu[0-9]+' /proc/stat)
        disk_available=$$(df / | tail -1 | awk '{print $$4}')
        warning_resources="false"
        if (( mem_available < 4000 )) ; then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough memory available for Docker.\e[0m"
          echo "At least 4GB of memory required. You have $$(numfmt --to iec $$((mem_available * one_meg)))"
          echo
          warning_resources="true"
        fi
        if (( cpus_available < 2 )); then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough CPUS available for Docker.\e[0m"
          echo "At least 2 CPUs recommended. You have $${cpus_available}"
          echo
          warning_resources="true"
        fi
        if (( disk_available < one_meg * 10 )); then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough Disk space available for Docker.\e[0m"
          echo "At least 10 GBs recommended. You have $$(numfmt --to iec $$((disk_available * 1024 )))"
          echo
          warning_resources="true"
        fi
        if [[ $${warning_resources} == "true" ]]; then
          echo
          echo -e "\033[1;33mWARNING!!!: You have not enough resources to run Airflow (see above)!\e[0m"
          echo "Please follow the instructions to increase amount of resources available:"
          echo "   https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#before-you-begin"
          echo
        fi
        mkdir -p /sources/logs /sources/dags /sources/plugins
        chown -R "${AIRFLOW_UID}:0" /sources/{logs,dags,plugins}
        exec /entrypoint airflow version
    # yamllint enable rule:line-length
    environment:
      <<: *airflow-common-env
      _AIRFLOW_DB_UPGRADE: 'true'
      _AIRFLOW_WWW_USER_CREATE: 'true'
      _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow}
      _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow}
      _PIP_ADDITIONAL_REQUIREMENTS: ''
    user: "0:0"
    volumes:
      - ${AIRFLOW_PROJ_DIR:-.}:/sources

  airflow-cli:
    <<: *airflow-common
    profiles:
      - debug
    environment:
      <<: *airflow-common-env
      CONNECTION_CHECK_MAX_COUNT: "0"
    # Workaround for entrypoint issue. See: https://github.com/apache/airflow/issues/16252
    command:
      - bash
      - -c
      - airflow

  # You can enable flower by adding "--profile flower" option e.g. docker-compose --profile flower up
  # or by explicitly targeted on the command line e.g. docker-compose up flower.
  # See: https://docs.docker.com/compose/profiles/
  flower:
    <<: *airflow-common
    command: celery flower
    profiles:
      - flower
    ports:
      - "5555:5555"
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:5555/"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

volumes:
  postgres-db-volume:

2. 不要な部分をコメントアウト

不要な部分をコメントアウトしたのがこれ。Celery 関連、Redis関連をコメントアウトしています。

version: '3.8'
x-airflow-common:
  &airflow-common
  # In order to add custom dependencies or upgrade provider packages you can use your extended image.
  # Comment the image line, place your Dockerfile in the directory where you placed the docker-compose.yaml
  # and uncomment the "build" line below, Then run `docker-compose build` to build the images.
  image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:2.6.1}
  # build: .
  environment:
    &airflow-common-env
    AIRFLOW__CORE__EXECUTOR: LocalExecutor
    AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    # For backward compatibility, with Airflow <2.3
    AIRFLOW__CORE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    # AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@postgres/airflow
    # AIRFLOW__CELERY__BROKER_URL: redis://:@redis:6379/0
    AIRFLOW__CORE__FERNET_KEY: ''
    AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true'
    AIRFLOW__CORE__LOAD_EXAMPLES: 'true'
    AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth,airflow.api.auth.backend.session'
    # yamllint disable rule:line-length
    # Use simple http server on scheduler for health checks
    # See https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/check-health.html#scheduler-health-check-server
    # yamllint enable rule:line-length
    AIRFLOW__SCHEDULER__ENABLE_HEALTH_CHECK: 'true'
    # WARNING: Use _PIP_ADDITIONAL_REQUIREMENTS option ONLY for a quick checks
    # for other purpose (development, test and especially production usage) build/extend Airflow image.
    _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:-}
  volumes:
    - ${AIRFLOW_PROJ_DIR:-.}/dags:/opt/airflow/dags
    - ${AIRFLOW_PROJ_DIR:-.}/logs:/opt/airflow/logs
    - ${AIRFLOW_PROJ_DIR:-.}/config:/opt/airflow/config
    - ${AIRFLOW_PROJ_DIR:-.}/plugins:/opt/airflow/plugins
  user: "${AIRFLOW_UID:-50000}:0"
  depends_on:
    &airflow-common-depends-on
    # redis:
    #   condition: service_healthy
    postgres:
      condition: service_healthy

services:
  postgres:
    image: postgres:13
    environment:
      POSTGRES_USER: airflow
      POSTGRES_PASSWORD: airflow
      POSTGRES_DB: airflow
    volumes:
      - postgres-db-volume:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "airflow"]
      interval: 10s
      retries: 5
      start_period: 5s
    restart: always

  # redis:
  #   image: redis:latest
  #   expose:
  #     - 6379
  #   healthcheck:
  #     test: ["CMD", "redis-cli", "ping"]
  #     interval: 10s
  #     timeout: 30s
  #     retries: 50
  #     start_period: 30s
  #   restart: always

  airflow-webserver:
    <<: *airflow-common
    command: webserver
    ports:
      - "8080:8080"
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-scheduler:
    <<: *airflow-common
    command: scheduler
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8974/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  # airflow-worker:
  #   <<: *airflow-common
  #   command: celery worker
  #   healthcheck:
  #     test:
  #       - "CMD-SHELL"
  #       - 'celery --app airflow.executors.celery_executor.app inspect ping -d "celery@$${HOSTNAME}"'
  #     interval: 30s
  #     timeout: 10s
  #     retries: 5
  #     start_period: 30s
  #   environment:
  #     <<: *airflow-common-env
  #     # Required to handle warm shutdown of the celery workers properly
  #     # See https://airflow.apache.org/docs/docker-stack/entrypoint.html#signal-propagation
  #     DUMB_INIT_SETSID: "0"
  #   restart: always
  #   depends_on:
  #     <<: *airflow-common-depends-on
  #     airflow-init:
  #       condition: service_completed_successfully

  airflow-triggerer:
    <<: *airflow-common
    command: triggerer
    healthcheck:
      test: ["CMD-SHELL", 'airflow jobs check --job-type TriggererJob --hostname "$${HOSTNAME}"']
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-init:
    <<: *airflow-common
    entrypoint: /bin/bash
    # yamllint disable rule:line-length
    command:
      - -c
      - |
        function ver() {
          printf "%04d%04d%04d%04d" $${1//./ }
        }
        airflow_version=$$(AIRFLOW__LOGGING__LOGGING_LEVEL=INFO && gosu airflow airflow version)
        airflow_version_comparable=$$(ver $${airflow_version})
        min_airflow_version=2.2.0
        min_airflow_version_comparable=$$(ver $${min_airflow_version})
        if (( airflow_version_comparable < min_airflow_version_comparable )); then
          echo
          echo -e "\033[1;31mERROR!!!: Too old Airflow version $${airflow_version}!\e[0m"
          echo "The minimum Airflow version supported: $${min_airflow_version}. Only use this or higher!"
          echo
          exit 1
        fi
        if [[ -z "${AIRFLOW_UID}" ]]; then
          echo
          echo -e "\033[1;33mWARNING!!!: AIRFLOW_UID not set!\e[0m"
          echo "If you are on Linux, you SHOULD follow the instructions below to set "
          echo "AIRFLOW_UID environment variable, otherwise files will be owned by root."
          echo "For other operating systems you can get rid of the warning with manually created .env file:"
          echo "    See: https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#setting-the-right-airflow-user"
          echo
        fi
        one_meg=1048576
        mem_available=$$(($$(getconf _PHYS_PAGES) * $$(getconf PAGE_SIZE) / one_meg))
        cpus_available=$$(grep -cE 'cpu[0-9]+' /proc/stat)
        disk_available=$$(df / | tail -1 | awk '{print $$4}')
        warning_resources="false"
        if (( mem_available < 4000 )) ; then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough memory available for Docker.\e[0m"
          echo "At least 4GB of memory required. You have $$(numfmt --to iec $$((mem_available * one_meg)))"
          echo
          warning_resources="true"
        fi
        if (( cpus_available < 2 )); then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough CPUS available for Docker.\e[0m"
          echo "At least 2 CPUs recommended. You have $${cpus_available}"
          echo
          warning_resources="true"
        fi
        if (( disk_available < one_meg * 10 )); then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough Disk space available for Docker.\e[0m"
          echo "At least 10 GBs recommended. You have $$(numfmt --to iec $$((disk_available * 1024 )))"
          echo
          warning_resources="true"
        fi
        if [[ $${warning_resources} == "true" ]]; then
          echo
          echo -e "\033[1;33mWARNING!!!: You have not enough resources to run Airflow (see above)!\e[0m"
          echo "Please follow the instructions to increase amount of resources available:"
          echo "   https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#before-you-begin"
          echo
        fi
        mkdir -p /sources/logs /sources/dags /sources/plugins
        chown -R "${AIRFLOW_UID}:0" /sources/{logs,dags,plugins}
        exec /entrypoint airflow version
    # yamllint enable rule:line-length
    environment:
      <<: *airflow-common-env
      _AIRFLOW_DB_UPGRADE: 'true'
      _AIRFLOW_WWW_USER_CREATE: 'true'
      _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow}
      _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow}
      _PIP_ADDITIONAL_REQUIREMENTS: ''
    user: "0:0"
    volumes:
      - ${AIRFLOW_PROJ_DIR:-.}:/sources

  airflow-cli:
    <<: *airflow-common
    profiles:
      - debug
    environment:
      <<: *airflow-common-env
      CONNECTION_CHECK_MAX_COUNT: "0"
    # Workaround for entrypoint issue. See: https://github.com/apache/airflow/issues/16252
    command:
      - bash
      - -c
      - airflow

  # You can enable flower by adding "--profile flower" option e.g. docker-compose --profile flower up
  # or by explicitly targeted on the command line e.g. docker-compose up flower.
  # See: https://docs.docker.com/compose/profiles/
  # flower:
  #   <<: *airflow-common
  #   command: celery flower
  #   profiles:
  #     - flower
  #   ports:
  #     - "5555:5555"
  #   healthcheck:
  #     test: ["CMD", "curl", "--fail", "http://localhost:5555/"]
  #     interval: 30s
  #     timeout: 10s
  #     retries: 5
  #     start_period: 30s
  #   restart: always
  #   depends_on:
  #     <<: *airflow-common-depends-on
  #     airflow-init:
  #       condition: service_completed_successfully

volumes:
  postgres-db-volume:
% docker-compose up airflow-init
% docker-compose up -d

3. Postgresql の部分を MySQL へ変更

version: '3.8'
x-airflow-common:
  &airflow-common
  # In order to add custom dependencies or upgrade provider packages you can use your extended image.
  # Comment the image line, place your Dockerfile in the directory where you placed the docker-compose.yaml
  # and uncomment the "build" line below, Then run `docker-compose build` to build the images.
  image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:2.6.1}
  # build: .
  environment:
    &airflow-common-env
    AIRFLOW__CORE__EXECUTOR: LocalExecutor
    # AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: mysql://airflow:airflowpassword@mysql/airflow
    # For backward compatibility, with Airflow <2.3
    # AIRFLOW__CORE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    AIRFLOW__CORE__SQL_ALCHEMY_CONN: mysql://airflow:airflowpassword@mysql/airflow
    # AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@postgres/airflow
    # AIRFLOW__CELERY__BROKER_URL: redis://:@redis:6379/0
    AIRFLOW__CORE__FERNET_KEY: ''
    AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true'
    AIRFLOW__CORE__LOAD_EXAMPLES: 'true'
    AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth,airflow.api.auth.backend.session'
    # yamllint disable rule:line-length
    # Use simple http server on scheduler for health checks
    # See https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/check-health.html#scheduler-health-check-server
    # yamllint enable rule:line-length
    AIRFLOW__SCHEDULER__ENABLE_HEALTH_CHECK: 'true'
    # WARNING: Use _PIP_ADDITIONAL_REQUIREMENTS option ONLY for a quick checks
    # for other purpose (development, test and especially production usage) build/extend Airflow image.
    _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:-}
  volumes:
    - ${AIRFLOW_PROJ_DIR:-.}/dags:/opt/airflow/dags
    - ${AIRFLOW_PROJ_DIR:-.}/logs:/opt/airflow/logs
    - ${AIRFLOW_PROJ_DIR:-.}/config:/opt/airflow/config
    - ${AIRFLOW_PROJ_DIR:-.}/plugins:/opt/airflow/plugins
  user: "${AIRFLOW_UID:-50000}:0"
  depends_on:
    &airflow-common-depends-on
    # redis:
    #   condition: service_healthy
    # postgres:
    #   condition: service_healthy
    mysql:
      condition: service_healthy

services:
  # postgres:
  #   image: postgres:13
  #   environment:
  #     POSTGRES_USER: airflow
  #     POSTGRES_PASSWORD: airflow
  #     POSTGRES_DB: airflow
  #   volumes:
  #     - postgres-db-volume:/var/lib/postgresql/data
  #   healthcheck:
  #     test: ["CMD", "pg_isready", "-U", "airflow"]
  #     interval: 10s
  #     retries: 5
  #     start_period: 5s
  #   restart: always
  mysql:
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: airflow
      MYSQL_USER: airflow
      MYSQL_PASSWORD: airflowpassword
    volumes:
      - mysql-db-volume:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
      interval: 20s
      timeout: 10s
      retries: 5
    ports:
      - "13306:3306"

  # redis:
  #   image: redis:latest
  #   expose:
  #     - 6379
  #   healthcheck:
  #     test: ["CMD", "redis-cli", "ping"]
  #     interval: 10s
  #     timeout: 30s
  #     retries: 50
  #     start_period: 30s
  #   restart: always

  airflow-webserver:
    <<: *airflow-common
    command: webserver
    ports:
      - "8080:8080"
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-scheduler:
    <<: *airflow-common
    command: scheduler
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8974/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  # airflow-worker:
  #   <<: *airflow-common
  #   command: celery worker
  #   healthcheck:
  #     test:
  #       - "CMD-SHELL"
  #       - 'celery --app airflow.executors.celery_executor.app inspect ping -d "celery@$${HOSTNAME}"'
  #     interval: 30s
  #     timeout: 10s
  #     retries: 5
  #     start_period: 30s
  #   environment:
  #     <<: *airflow-common-env
  #     # Required to handle warm shutdown of the celery workers properly
  #     # See https://airflow.apache.org/docs/docker-stack/entrypoint.html#signal-propagation
  #     DUMB_INIT_SETSID: "0"
  #   restart: always
  #   depends_on:
  #     <<: *airflow-common-depends-on
  #     airflow-init:
  #       condition: service_completed_successfully

  airflow-triggerer:
    <<: *airflow-common
    command: triggerer
    healthcheck:
      test: ["CMD-SHELL", 'airflow jobs check --job-type TriggererJob --hostname "$${HOSTNAME}"']
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-init:
    <<: *airflow-common
    entrypoint: /bin/bash
    # yamllint disable rule:line-length
    command:
      - -c
      - |
        function ver() {
          printf "%04d%04d%04d%04d" $${1//./ }
        }
        airflow_version=$$(AIRFLOW__LOGGING__LOGGING_LEVEL=INFO && gosu airflow airflow version)
        airflow_version_comparable=$$(ver $${airflow_version})
        min_airflow_version=2.2.0
        min_airflow_version_comparable=$$(ver $${min_airflow_version})
        if (( airflow_version_comparable < min_airflow_version_comparable )); then
          echo
          echo -e "\033[1;31mERROR!!!: Too old Airflow version $${airflow_version}!\e[0m"
          echo "The minimum Airflow version supported: $${min_airflow_version}. Only use this or higher!"
          echo
          exit 1
        fi
        if [[ -z "${AIRFLOW_UID}" ]]; then
          echo
          echo -e "\033[1;33mWARNING!!!: AIRFLOW_UID not set!\e[0m"
          echo "If you are on Linux, you SHOULD follow the instructions below to set "
          echo "AIRFLOW_UID environment variable, otherwise files will be owned by root."
          echo "For other operating systems you can get rid of the warning with manually created .env file:"
          echo "    See: https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#setting-the-right-airflow-user"
          echo
        fi
        one_meg=1048576
        mem_available=$$(($$(getconf _PHYS_PAGES) * $$(getconf PAGE_SIZE) / one_meg))
        cpus_available=$$(grep -cE 'cpu[0-9]+' /proc/stat)
        disk_available=$$(df / | tail -1 | awk '{print $$4}')
        warning_resources="false"
        if (( mem_available < 4000 )) ; then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough memory available for Docker.\e[0m"
          echo "At least 4GB of memory required. You have $$(numfmt --to iec $$((mem_available * one_meg)))"
          echo
          warning_resources="true"
        fi
        if (( cpus_available < 2 )); then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough CPUS available for Docker.\e[0m"
          echo "At least 2 CPUs recommended. You have $${cpus_available}"
          echo
          warning_resources="true"
        fi
        if (( disk_available < one_meg * 10 )); then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough Disk space available for Docker.\e[0m"
          echo "At least 10 GBs recommended. You have $$(numfmt --to iec $$((disk_available * 1024 )))"
          echo
          warning_resources="true"
        fi
        if [[ $${warning_resources} == "true" ]]; then
          echo
          echo -e "\033[1;33mWARNING!!!: You have not enough resources to run Airflow (see above)!\e[0m"
          echo "Please follow the instructions to increase amount of resources available:"
          echo "   https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#before-you-begin"
          echo
        fi
        mkdir -p /sources/logs /sources/dags /sources/plugins
        chown -R "${AIRFLOW_UID}:0" /sources/{logs,dags,plugins}
        exec /entrypoint airflow version
    # yamllint enable rule:line-length
    environment:
      <<: *airflow-common-env
      _AIRFLOW_DB_UPGRADE: 'true'
      _AIRFLOW_WWW_USER_CREATE: 'true'
      _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow}
      _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow}
      _PIP_ADDITIONAL_REQUIREMENTS: ''
    user: "0:0"
    volumes:
      - ${AIRFLOW_PROJ_DIR:-.}:/sources

  airflow-cli:
    <<: *airflow-common
    profiles:
      - debug
    environment:
      <<: *airflow-common-env
      CONNECTION_CHECK_MAX_COUNT: "0"
    # Workaround for entrypoint issue. See: https://github.com/apache/airflow/issues/16252
    command:
      - bash
      - -c
      - airflow

  # You can enable flower by adding "--profile flower" option e.g. docker-compose --profile flower up
  # or by explicitly targeted on the command line e.g. docker-compose up flower.
  # See: https://docs.docker.com/compose/profiles/
  # flower:
  #   <<: *airflow-common
  #   command: celery flower
  #   profiles:
  #     - flower
  #   ports:
  #     - "5555:5555"
  #   healthcheck:
  #     test: ["CMD", "curl", "--fail", "http://localhost:5555/"]
  #     interval: 30s
  #     timeout: 10s
  #     retries: 5
  #     start_period: 30s
  #   restart: always
  #   depends_on:
  #     <<: *airflow-common-depends-on
  #     airflow-init:
  #       condition: service_completed_successfully

volumes:
  # postgres-db-volume:
  mysql-db-volume:

4. コメント部分を削除

コメント部分を削除したのがこちら。

version: '3.8'
x-airflow-common:
  &airflow-common
  # In order to add custom dependencies or upgrade provider packages you can use your extended image.
  # Comment the image line, place your Dockerfile in the directory where you placed the docker-compose.yaml
  # and uncomment the "build" line below, Then run `docker-compose build` to build the images.
  image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:2.6.1}
  # build: .
  environment:
    &airflow-common-env
    AIRFLOW__CORE__EXECUTOR: LocalExecutor
    AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: mysql://airflow:airflowpassword@mysql/airflow
    # For backward compatibility, with Airflow <2.3
    AIRFLOW__CORE__SQL_ALCHEMY_CONN: mysql://airflow:airflowpassword@mysql/airflow
    AIRFLOW__CORE__FERNET_KEY: ''
    AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true'
    AIRFLOW__CORE__LOAD_EXAMPLES: 'true'
    AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth,airflow.api.auth.backend.session'
    # yamllint disable rule:line-length
    # Use simple http server on scheduler for health checks
    # See https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/logging-monitoring/check-health.html#scheduler-health-check-server
    # yamllint enable rule:line-length
    AIRFLOW__SCHEDULER__ENABLE_HEALTH_CHECK: 'true'
    # WARNING: Use _PIP_ADDITIONAL_REQUIREMENTS option ONLY for a quick checks
    # for other purpose (development, test and especially production usage) build/extend Airflow image.
    _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:-}
  volumes:
    - ${AIRFLOW_PROJ_DIR:-.}/dags:/opt/airflow/dags
    - ${AIRFLOW_PROJ_DIR:-.}/logs:/opt/airflow/logs
    - ${AIRFLOW_PROJ_DIR:-.}/config:/opt/airflow/config
    - ${AIRFLOW_PROJ_DIR:-.}/plugins:/opt/airflow/plugins
  user: "${AIRFLOW_UID:-50000}:0"
  depends_on:
    &airflow-common-depends-on
    mysql:
      condition: service_healthy

services:
  mysql:
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: airflowpassword
      MYSQL_DATABASE: airflow
      MYSQL_USER: airflow
      MYSQL_PASSWORD: airflowpassword
    volumes:
      - mysql-db-volume:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
      interval: 20s
      timeout: 10s
      retries: 5
    ports:
      - "13306:3306"

  airflow-webserver:
    <<: *airflow-common
    command: webserver
    ports:
      - "8080:8080"
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-scheduler:
    <<: *airflow-common
    command: scheduler
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:8974/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-triggerer:
    <<: *airflow-common
    command: triggerer
    healthcheck:
      test: ["CMD-SHELL", 'airflow jobs check --job-type TriggererJob --hostname "$${HOSTNAME}"']
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully

  airflow-init:
    <<: *airflow-common
    entrypoint: /bin/bash
    # yamllint disable rule:line-length
    command:
      - -c
      - |
        function ver() {
          printf "%04d%04d%04d%04d" $${1//./ }
        }
        airflow_version=$$(AIRFLOW__LOGGING__LOGGING_LEVEL=INFO && gosu airflow airflow version)
        airflow_version_comparable=$$(ver $${airflow_version})
        min_airflow_version=2.2.0
        min_airflow_version_comparable=$$(ver $${min_airflow_version})
        if (( airflow_version_comparable < min_airflow_version_comparable )); then
          echo
          echo -e "\033[1;31mERROR!!!: Too old Airflow version $${airflow_version}!\e[0m"
          echo "The minimum Airflow version supported: $${min_airflow_version}. Only use this or higher!"
          echo
          exit 1
        fi
        if [[ -z "${AIRFLOW_UID}" ]]; then
          echo
          echo -e "\033[1;33mWARNING!!!: AIRFLOW_UID not set!\e[0m"
          echo "If you are on Linux, you SHOULD follow the instructions below to set "
          echo "AIRFLOW_UID environment variable, otherwise files will be owned by root."
          echo "For other operating systems you can get rid of the warning with manually created .env file:"
          echo "    See: https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#setting-the-right-airflow-user"
          echo
        fi
        one_meg=1048576
        mem_available=$$(($$(getconf _PHYS_PAGES) * $$(getconf PAGE_SIZE) / one_meg))
        cpus_available=$$(grep -cE 'cpu[0-9]+' /proc/stat)
        disk_available=$$(df / | tail -1 | awk '{print $$4}')
        warning_resources="false"
        if (( mem_available < 4000 )) ; then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough memory available for Docker.\e[0m"
          echo "At least 4GB of memory required. You have $$(numfmt --to iec $$((mem_available * one_meg)))"
          echo
          warning_resources="true"
        fi
        if (( cpus_available < 2 )); then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough CPUS available for Docker.\e[0m"
          echo "At least 2 CPUs recommended. You have $${cpus_available}"
          echo
          warning_resources="true"
        fi
        if (( disk_available < one_meg * 10 )); then
          echo
          echo -e "\033[1;33mWARNING!!!: Not enough Disk space available for Docker.\e[0m"
          echo "At least 10 GBs recommended. You have $$(numfmt --to iec $$((disk_available * 1024 )))"
          echo
          warning_resources="true"
        fi
        if [[ $${warning_resources} == "true" ]]; then
          echo
          echo -e "\033[1;33mWARNING!!!: You have not enough resources to run Airflow (see above)!\e[0m"
          echo "Please follow the instructions to increase amount of resources available:"
          echo "   https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#before-you-begin"
          echo
        fi
        mkdir -p /sources/logs /sources/dags /sources/plugins
        chown -R "${AIRFLOW_UID}:0" /sources/{logs,dags,plugins}
        exec /entrypoint airflow version
    # yamllint enable rule:line-length
    environment:
      <<: *airflow-common-env
      _AIRFLOW_DB_UPGRADE: 'true'
      _AIRFLOW_WWW_USER_CREATE: 'true'
      _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow}
      _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow}
      _PIP_ADDITIONAL_REQUIREMENTS: ''
    user: "0:0"
    volumes:
      - ${AIRFLOW_PROJ_DIR:-.}:/sources

  airflow-cli:
    <<: *airflow-common
    profiles:
      - debug
    environment:
      <<: *airflow-common-env
      CONNECTION_CHECK_MAX_COUNT: "0"
    # Workaround for entrypoint issue. See: https://github.com/apache/airflow/issues/16252
    command:
      - bash
      - -c
      - airflow

volumes:
  mysql-db-volume:
% docker-compose up airflow-init
% docker-compose up -d

Docker コンテナで Apache Airflow を使ってみる

  1. 任意のディレクトリを作る
  2. Docker Compose ファイルをダウンロードし、実行する
  3. DAG を追加する
  4. 再起動して確認する
  5. DAG を実行する

1. 任意のディレクトリを作る

% mkdir docker_airflow
% cd docker_airflow

2. Docker Compose ファイルをダウンロードし、プロジェクトを実行する

% curl -LfO 'https://airflow.apache.org/docs/apache-airflow/2.6.1/docker-compose.yaml'
% mkdir -p ./dags ./logs ./plugins ./config
% docker-compose up airflow-init
% docker-compose up -d

ブラウザで「http://127.0.0.1:8080」を開いてログインします。

3. DAG を追加する

プロジェクトディレクトリ直下の「dags」に下記の my_dag.py を追加しました。

色々書いてありますが、下の方で「bash_command='echo "Hello, Airflow!"'」となっている通り、Hello, Airflow! と表出するプログラムになっています。

# my_dag.py

from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from datetime import datetime

default_args = {
    'owner': 'airflow',
    'start_date': datetime(2023, 1, 1),
}

with DAG(dag_id='my_dag', schedule_interval='@daily', default_args=default_args) as dag:
    task1 = BashOperator(
        task_id='task1',
        bash_command='echo "Hello, Airflow!"'
    )

    task1

4. プロジェクトを再起動する

% docker-compose down
% docker-compose up -d

再度ブラウザで「http://127.0.0.1:8080」を開いてログインします。

5. DAG を実行する

で、「my_dag」を実行してみます。

実行が完了したらログを見てみます。

「Hello Airflow!」と表出されています。新たに追加した DAG がきちんと機能しています。

[2023-05-23, 08:36:59 UTC] {subprocess.py:75} INFO - Running command: ['/bin/bash', '-c', 'echo "Hello, Airflow!"']
[2023-05-23, 08:36:59 UTC] {subprocess.py:86} INFO - Output:
[2023-05-23, 08:36:59 UTC] {subprocess.py:93} INFO - Hello, Airflow!
[2023-05-23, 08:36:59 UTC] {subprocess.py:97} INFO - Command exited with return code 0

地下ペディアを Docker コンテナに入れてみた

Docker を勉強する上で、とりあえず何パターンか作業をしたいと思い、いくつかやってきました。

で、次は既にある程度作り込まれているものをコンテナに入れてみようと思い、過去に自分が作った物で企業との面談等でも触れていただくことの多い「地下ぺディア」を使ってやってみることにしました。

ゴールとしてはとりあえず Docker イメージを run してブラウザで 127.0.0.1:8000 を開けば地下ぺディアが使える様にします。

  1. 地下ぺディアとは
  2. Dockerfile を作る
  3. docker build & docker run
  4. 動作確認
  5. ローカルで修正して build & run

1. 地下ぺディアとは

そもそも「地下ぺディア」とは、自然言語処理の技術の一つである形態素解析を使って、ウィキペディアの記事をカイジっぽい文体で表示する Web アプリです。

フレームワークに Django、形態素解析には CaboCha を使用しており、任意のウィキペディアページの HTML ソースを解析、HTML 要素を崩さずに文体を変更し HTTP レスポンスとして返す様になっています。

元々は「ウィキペディア記事を元に自由ミルクボーイの漫才を作れたら。。」と思い立ったものの難しそうだったのでひとまず地下ぺディアという形にしたという経緯があります。

2. Dockerfile を作る

元々地下ぺディアのファイル群があるディレクトリに Dockerfile を作成します。

ベースイメージとしてはこちらの記事で作成した、Python で CaboCha を使える様にしたものを使います。

FROM docker_nlp:1.0

# ファイルを全てコピーし、requirements.txt で pip install を実施
WORKDIR /app
COPY . .
RUN pip3 install -r requirements.txt

# コンテナ外からのアクセスを可能にするため 0.0.0.0 番で runserver を実行
# 開発環境用の settings_dev.py を使用
CMD ["python3", "chikapedia/manage.py", "runserver", "0.0.0.0:8000","--settings=chikapedia.settings_dev"]

CMD の部分で Django の runserver を実行する様記述していますが、「0.0.0.0:8000」としてコンテナ外(つまりホストから)からのアクセスを受け付ける様にし、「--settings=chikapedia.settings_dev」で開発環境用の settings.py を使用できる様にしています。

あとはいつも通りです。

3. docker build & docker run

% docker build -t chikadocker:1.0 .
% docker run --name chikapedia-docker -p 8000:8000 -it chikadocker:1.0
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
May 19, 2023 - 02:22:08
Django version 3.2.4, using settings 'chikapedia.settings_dev'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

未 migrate のマイグレーションに関する警告が出ますが、地下ぺディアはデータベースを使わないので無視します。

run は無事完了し、Django のサーバーもコンテナ内「0.0.0.0:8000」で立ち上がりました。

4. 動作確認

果たして動くのか?ローカル PC のブラウザで 127.0.0.1:8000 にアクセスしてみます。

無事動きました!

5. ローカルで修正して build & run

バグ修正時の対応としては

  1. ローカルでコード修正
  2. docker build でイメージ更新
  3. docker rm でコンテナ削除
  4. docker run でコンテナ作成
  5. 動作確認

といった流れで修正と確認を繰り返しました。

Python × 自然言語処理の環境を Docker コンテナに入れてみた

Docker の勉強がてら、Python での自然言語処理によく使われる CaboCha モジュールを使える Docker コンテナを作ったので手順を記しておきます。

イメージは Docker Hub のリポジトリに push してあります。

ちなみに CaboCha モジュールは単純に pip install で使える様なものではなく、条件付き確率場の自然言語処理向け実装である(CRF++)や、辞書ファイル(mecab-ipadic-neologd)などをインストールする必要があり面倒な印象です。

  1. Dockerfile を作ってみた
  2. CRF++ のダウンロード
  3. Docker イメージを作ってみた
  4. コンテナを作ってみた
  5. Python で CaboCha を使ってみた

Dockerfile を作ってみた

まず Dockerfile を作ります。というか今回ここが一番大事なところです。

以前↓の記事で VPS の Ubuntu に環境を構築したことがあるので、基本的にはその手順を流用しました。

そして ChatGPT の手を多分に借りました。

下記が Dockerfile の中身です。

# Dockerfile

# Use Ubuntu 20.04 as a base
FROM ubuntu:20.04

# Set environment variables
ENV DEBIAN_FRONTEND=noninteractive

# Update system packages
RUN apt-get update && apt-get install -y \
    build-essential \
    mecab \
    libmecab-dev \
    mecab-ipadic \
    git \
    wget \
    curl \
    bzip2 \
    python3 \
    python3-pip \
    sudo

# Install mecab-ipadic-neologd
WORKDIR /var/lib/mecab/dic
RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
RUN ./mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -y

# Install CRF++
WORKDIR /root
COPY CRF++-0.58.tar .
RUN tar xvf CRF++-0.58.tar && \
    cd CRF++-0.58 && \
    ./configure && make && make install && \
    ldconfig && \
    rm ../CRF++-0.58.tar

# Install CaboCha
WORKDIR /root
RUN FILE_ID=0B4y35FiV1wh7SDd1Q1dUQkZQaUU && \
    FILE_NAME=cabocha-0.69.tar.bz2 && \
    curl -sc /tmp/cookie "https://drive.google.com/uc?export=download&id=${FILE_ID}" > /dev/null && \
    CODE="$(awk '/_warning_/ {print $NF}' /tmp/cookie)" && \
    curl -Lb /tmp/cookie "https://drive.google.com/uc?export=download&confirm=${CODE}&id=${FILE_ID}" -o ${FILE_NAME} && \
    bzip2 -dc cabocha-0.69.tar.bz2 | tar xvf - && \
    cd cabocha-0.69 && \
    ./configure --with-mecab-config=`which mecab-config` --with-charset=UTF8 && \
    make && make check && make install && \
    ldconfig && \
    cd python && python3 setup.py install

# Install mecab-python3
RUN pip3 install mecab-python3

# Cleanup apt cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Set default work directory
WORKDIR /root

CMD ["/bin/bash"]

CRF++ ファイルのダウンロード

上記 Dockerfile の中に「COPY CRF++-0.58.tar .」の記述があります。

このファイルはこちらのリンクから直接ダウンロードしておく必要があったので、ダウンロードして Dockerfile と同じディレクトリに配置しました。

% ls
CRF++-0.58.tar	Dockerfile

Docker イメージを作ってみた

で、docker build でイメージを作成します。

% docker build -t python-cabocha:1.0 .

ここが成功すればあとはどうとでもなる気がします。

コンテナを作ってみた

「docker run」でコンテナを作成するとそのままコンテナ内に入ります。

% docker run --name cabocha-python -it python-cabocha:1.0
root@21c443991ed9:~#

Python で CaboCha を使ってみた

コンテナ内で Python を起動します。

root@21c443991ed9:~# python3

で、CaboCha を使ってみます。

>>> import CaboCha
>>> sentence = 'エンゼルスの大谷翔平投手が「3番・DH」でスタメン出場。前日に続き4打数無安打と2試合連続ノーヒットとなった。'
>>> c = CaboCha.Parser('-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd')
>>> print(c.parseToString(sentence))
         エンゼルスの-D              
         大谷翔平投手が---D          
            「3番・DH」で-D          
             スタメン出場。---------D
                       前日に-D     |
                           続き-----D
                    4打数無安打と---D
              2試合連続ノーヒットと-D
                             なった。
EOS

>>> 

無事使えました!

【Mac】Dockerfile を作成、イメージを作成しコンテナで Python 実行

Docker Hub で公開されているイメージを元に、自作の Python プログラムを含めて Docker イメージを作成する工程をメモしておきます。

作成したイメージからコンテナを作成、コンテナ内で Python プログラムを実行するところまでカバーしています。

前提:Mac で Docker Desktop をインストール済み

  1. Python 関連の準備
    1. Python プログラムの準備
    2. requirements.txt の作成
  2. Docker 関連の準備
    1. Dockerfile の作成
    2. .dockerignore の作成
    3. docker build でイメージを作成
  3. コンテナの作成と Python プログラムの実行
    1. docker run でイメージを実行
    2. コンテナに入ってみる
    3. Python プログラムを実行してみる
  4. コンテナ脱出、コンテナ停止、コンテナ削除

1. Python 関連の準備

1. Python プログラムの準備

まずは下準備として Python 仮想環境をローカル環境に作り、コンテナ内で実行したい Python プログラムを用意します。

とりあえず手持ちのプログラム「amazon_scraping.py」を使いまわします。仮想環境と横並びで配置しています。

% ls
amazon_scraping.py        venv

Amazon の商品リストページの情報をスクレイピングして CSV ファイルにアウトプットするプログラムです。

# amazon_scraping.py

from datetime import date
from time import sleep
import csv
import requests
from bs4 import BeautifulSoup


domain_name = 'amazon.co.jp'
search_term = 'iPhone 12'

url = f'https://www.{domain_name}/s?k={search_term}'.replace(' ','+')
urls = []
for i in range(1,2):
    urls.append(f'{url}&page={i}')

headers = {
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15',
    'Host':f'www.{domain_name}'
}

# Request each URL, convert into bs4.BeautifulSoup
soups = []
for url in urls:
    response = requests.get(url,headers=headers)
    soup = BeautifulSoup(response.content, 'html.parser')
    soups.append(soup)
    sleep(0.5)

# Convert delivery date
def format_delivery_date(date_string):
    if len(date_string) == 0:
        return None
    
    if date_string[-2:] == '曜日':
        date_string = date_string[:-3]

    if date_string[0:3] == '明日中' and date_string[-4:] == '1月/1':
        date_string = date_string.replace('明日中','2024/')
        
    elif date_string[0:3] == '明日中':
        date_string = date_string.replace('明日中','2023/')
    
    date_string = date_string.replace('月/','/')
        
    return date_string

# Extract data from bs4.BeautifulSoup
def scan_page(soup, page_num):
    products = []

    for product in soup.select('.s-result-item'):
        asin = product['data-asin']

        a_spacing_top_small = product.select_one('.a-spacing-top-small')
        a_section_list = product.select('.sg-row .a-section')

        for a_section in a_section_list:
            price_elem = a_section.select_one('.a-price .a-offscreen')        
            if price_elem:
                price = int(price_elem.get_text().replace('¥', '').replace(',',''))
                continue

            delivery_date_by = a_section.select_one('span:-soup-contains("までにお届け")')
            if delivery_date_by:
                delivery_date = format_delivery_date(a_section.select('span')[1].text)
                continue

        if asin:
            products.append({'asin': asin, 'price': price, 'delivery_date': delivery_date, 'page_number': page_num})

    return products

for page_num, soup in enumerate(soups):
    dict_list = scan_page(soup, page_num+1)
    fieldnames = dict_list[0].keys()
    
    with open('output.csv', 'w', newline='') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(dict_list)
    
print('csv file created')

標準ではないモジュールとして、requests と beautifulsoup4 を pip でインストールします。

(venv) % pip install requests
(venv) % pip install beautifulsoup4

2. requirements.txt の作成

次に requirements.txt を作ります。

% pip freeze > requirements.txt
(venv) % ls
amazon_scraping.py      requirements.txt        venv

中身は下記の様になっています。

# requirements.txt
beautifulsoup4==4.12.2
certifi==2023.5.7
charset-normalizer==3.1.0
idna==3.4
requests==2.30.0
soupsieve==2.4.1
urllib3==2.0.2

3. .dockerignore の作成

「venv」配下は必要ないため、イメージに含めない様「.dockerignore」に追加します。

# .dockerignore
venv

とりあえず Python プログラムの下準備はここまでです。

2. Docker 関連の準備

1. Dockerfile を作る

仮想環境のディレクトリと横並びで Dockerfile というファイルを作成します。

ubuntu:20.04 のイメージを元に、Python および使用するモジュールをインストールする様記述します。

# Dockerfile
FROM ubuntu:20.04

#apt の最新化の後 python と pip をインストール
RUN apt update 
RUN apt install -y python3.9 
RUN apt install -y python3-pip

# 作業ディレクトリを /var に移動
WORKDIR /var

# ローカル環境の amazon_scraping.py をコンテナへコピー
COPY amazon_scraping.py .

# ローカル環境の requirements.txt をコンテナへコピーし、中身を pip install
COPY requirements.txt .
RUN python3.9 -m pip install -r requirements.txt

中身は上記の通りで、ubuntu:20.04 のイメージを元に、ファイルのコピーやインストールをおこないます。

「ls」を実行すると下記の状態です。

(venv) % ls
Dockerfile              amazon_scraping.py      requirements.txt        venv

2. docker build でイメージを作成

「docker build」コマンドを実行します。

% docker build -t docker_amzn:1.0 .
[+] Building 239.4s (13/13) FINISHED                                                                                                              
 => [internal] load build definition from Dockerfile                                                                                         0.0s
 => => transferring dockerfile: 253B                                                                                                         0.0s
 => [internal] load .dockerignore                                                                                                            0.0s
 => => transferring context: 2B                                                                                                              0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                                              0.9s
 => [1/8] FROM docker.io/library/ubuntu:20.04@sha256:db8bf6f4fb351aa7a26e27ba2686cf35a6a409f65603e59d4c203e58387dc6b3                        4.2s
 => => resolve docker.io/library/ubuntu:20.04@sha256:db8bf6f4fb351aa7a26e27ba2686cf35a6a409f65603e59d4c203e58387dc6b3                        0.0s
 => => sha256:db8bf6f4fb351aa7a26e27ba2686cf35a6a409f65603e59d4c203e58387dc6b3 1.13kB / 1.13kB                                               0.0s
 => => sha256:b795f8e0caaaacad9859a9a38fe1c78154f8301fdaf0872eaf1520d66d9c0b98 424B / 424B                                                   0.0s
 => => sha256:88bd6891718934e63638d9ca0ecee018e69b638270fe04990a310e5c78ab4a92 2.30kB / 2.30kB                                               0.0s
 => => sha256:ca1778b6935686ad781c27472c4668fc61ec3aeb85494f72deb1921892b9d39e 27.50MB / 27.50MB                                             2.9s
 => => extracting sha256:ca1778b6935686ad781c27472c4668fc61ec3aeb85494f72deb1921892b9d39e                                                    0.9s
 => [internal] load build context                                                                                                            0.0s
 => => transferring context: 2.67kB                                                                                                          0.0s
 => [2/8] RUN apt update                                                                                                                    62.4s
 => [3/8] RUN apt install -y python3.9                                                                                                      35.9s
 => [4/8] RUN apt install -y python3-pip                                                                                                   130.6s
 => [5/8] COPY requirements.txt .                                                                                                            0.0s
 => [6/8] RUN python3.9 -m pip install -r requirements.txt                                                                                   2.7s
 => [7/8] WORKDIR /var                                                                                                                       0.0s
 => [8/8] COPY /venv/amazon_scraping.py .                                                                                                    0.0s
 => exporting to image                                                                                                                       2.6s
 => => exporting layers                                                                                                                      2.6s
 => => writing image sha256:9f3dfca1f57b234294ed4666ea9d6dc05f7200cf30c6c10bbebf83834ae6e457                                                 0.0s
 => => naming to docker.io/library/docker_amzn:1.0                                                                                           0.0s
% 

数分かかりましたが無事完了。「docker images」コマンドで作成済みのイメージを確認できます。

% docker images
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
docker_amzn   1.0       9f3dfca1f57b   59 seconds ago   473MB

3. コンテナに入ってみる

1. docker run でイメージを実行

「docker run」コマンドで Docker イメージからコンテナを作成します。

コンテナ外からのアクセスはしないのでポートフォワーディング(-p オプション)は指定していません。

% docker run --name amzn_scraper -it -d docker_amzn:1.0
47caefa69121c3323c7379f448952003001817e937ffb3232d4564fce9b3c01c

2. コンテナに入ってみる

% docker exec -it amzn_scraper bash
root@47caefa69121:/var# 

「ls」を実行すると、Dockerfile で COPY の記述をした amazon_scraping.py や requirements.txt がコンテナに存在することを確認できます。

root@cd3f7f913010:/var# ls
amazon_scraping.py  backups  cache  lib  local  lock  log  mail  opt  requirements.txt  run  spool  tmp

3. Python プログラムを実行してみる

そのままコンテナ内で Python プログラムを実行してみます。

root@cd3f7f913010:/var# python3.9 amazon_scraping.py
csv file created

ファイルが作成された様です。

root@cd3f7f913010:/var# ls
amazon_scraping.py  backups  cache  lib  local  lock  log  mail  opt  output.csv  requirements.txt  run  spool  tmp

「head」コマンドで中身も確認。きちんと作成されている様です。

root@cd3f7f913010:/var# head output.csv
asin,price,delivery_date,page_number
B0BDHLR5WP,164800,,1
B0BDHYRRQX,134800,,1
B09M69W9KR,234801,,1
B09M68Y2HZ,162800,,1
B0928MGLCR,50025,,1
B0928LZ4HD,67980,2023/5/19,1
B08B9WMNSS,49490,,1
B0928L4D5H,92430,,1
B08B9GTC1T,78695,,1

8. コンテナ脱出、コンテナ停止、コンテナ削除

コンテナ脱出

# exit
exit

コンテナ停止

% docker stop amzn_scraper
amzn_scraper
% docker ps -a            
CONTAINER ID   IMAGE             COMMAND       CREATED          STATUS                     PORTS     NAMES
47caefa69121   docker_amzn:1.0   "/bin/bash"   13 minutes ago   Exited (0) 2 seconds ago             amzn_scraper

コンテナ削除

% docker rm amzn_scraper
amzn_scraper
% docker ps -a          
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

【Mac】Docker をインストールしてコンテナ内の MySQL を触ってみる

Docker を始めて使う際のプロセスをメモしておきます。

Macbook Pro に Docker Desktop をインストールし、Docker Hub から MySQL のコンテナイメージをダウンロードし起動する手順となります。

  1. Docker Desktop のインストール
  2. Docker Hub から Docker イメージをダウンロード
  3. コンテナの作成 & 起動
  4. コンテナに入ってみる
  5. コンテナから出る
  6. その他のコマンド
  7. コンテナに入らずにコンテナ内の MySQL にアクセスする
  8. データベースを作成する
  9. DBeaver で接続する

1. Docker Desktop のインストール

下記のリンクから Mac 用の Docker Desktop をダウンロードします。

https://docs.docker.com/desktop/install/mac-install/

「Docker.dmg」というファイルがダウンロードされるのでダブルクリックで実行し、画面表示に従ってインストールを進めます。

「docker --version」を実行してインストールが完了したか確認します。

% docker --version
Docker version 23.0.5, build bc4487a

無事インストールが出来た様です。

2. Docker Hub から Docker イメージをダウンロード

次に、「docker pull mysql」を実行して MySQL のコンテナイメージをダウンロードします。

ちなみに「実行場所にディレクトリが作られる」等はないので、ローカル環境のどこで実行しても大丈夫です。

% docker pull mysql
Using default tag: latest
latest: Pulling from library/mysql
328ba678bf27: Pull complete 
f3f5ff008d73: Pull complete 
dd7054d6d0c7: Pull complete 
70b5d4e8750e: Pull complete 
cdc4a7b43bdd: Pull complete 
a0608f8959e0: Pull complete 
5823e721608f: Pull complete 
a564ada930a9: Pull complete 
539565d00e89: Pull complete 
a11a06843fd5: Pull complete 
92f6d4aa041d: Pull complete 
Digest: sha256:a43f6e7e7f3a5e5b90f857fbed4e3103ece771b19f0f75880f767cf66bbb6577
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest

ダウンロードが完了しました。「docker images」コマンドを実行するとダウンロードされたコンテナイメージを確認することができます。

% docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
mysql        latest    8189e588b0e8   4 weeks ago   564MB

注意点

ちなみに、この時ローカルの Docker アプリケーションが起動していないと下記のエラーが出てしまいます。

% docker pull mysql
Using default tag: latest
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

この場合は Docker アプリケーションを起動して再度実行してください。

3. コンテナの作成 & 起動

Docker イメージのダウンロードの完了後、「docker run」コマンドでイメージからコンテナを作成、起動します。

Docker Hub のページに記載がある下記のフォーマットを元に適切な「docker run」コマンドを作成、実行します。

$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

主なオプション

上記のフォーマットに登場するオプションは下記の内容になります。

表記設定内容備考
--nameコンテナの識別子
-eコンテナ内の環境変数上記では MySQL のログインパスワードを設定
-dデタッチドモードバックグラウンドでコンテナを起動
mysql:tag使用する Docker イメージとそのバージョンの指定「イメージ:タグ」の形式で記述。タグのデフォルトは直近のもの。

加えて今回は -p オプションを設定し、ローカル PC からコンテナへのポート転送も設定しておきます。

表記設定内容備考
-pポートフォワーディング「ホストのポート:コンテナのポート」の形式で設定

今回使うコマンドは下記とします。

$ docker run --name sample-mysql -p 13306:3306 -e MYSQL_ROOT_PASSWORD=mysqlpassword -d mysql

「-p 13306:3306」の部分で、ホストの 13306 番ポートとコンテナの 3306 番ポートを紐づける設定にしています。

% docker run --name sample-mysql -p 13306:3306 -e MYSQL_ROOT_PASSWORD=mysqlpassword -d mysql
2b3b0efc8a7c7085e8e405dae4026a929a8d3cc76860f99dc9dde0b3f24474f1

「docker ps」コマンドを実行してコンテナが起動されているかを確認します。

% docker ps                                                                                 
CONTAINER ID   IMAGE     COMMAND                   CREATED          STATUS          PORTS                                NAMES
2b3b0efc8a7c   mysql     "docker-entrypoint.s…"   15 seconds ago   Up 14 seconds   33060/tcp, 0.0.0.0:13306->3306/tcp   sample-mysql

無事にコンテナが作成、起動されています。

ちなみに、先述した「docker pull」を実行しなくても、「docker run」実行時にローカル環境に対象のイメージがない場合、自動的に Docker Hub 上で探してくれます。

4. コンテナに入ってみる

「docker exec」コマンドを実行し、作成したコンテナの中に入ってみます。

% docker exec -it sample-mysql bash
bash-4.4# 
表記設定内容
-iti は STDIN(標準入力)の開放、t は擬似 TTY(標準入出力先デバイス)の割り当て。
sample-mysql対象のコンテナの名前
bashコンテナ内で使用するシェル
bash-4.4# mysql -u root -p   
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.33 MySQL Community Server - GPL

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)

とりあえずコンテナ内の MySQL を参照することはできました。

mysql> exit
Bye
bash-4.4# 

5. コンテナから出る

「exit」と入力すればコンテナから出られます。

bash-4.4# exit
exit
% 

6. その他のコマンド

取り急ぎいくつかのコマンドを記載しておきます。

コマンド表記用途
docker stop コンテナ名コンテナの停止
docker start コンテナ名コンテナの起動
docker ps起動中のコンテナの確認
docker ps -a作成されたコンテナの確認(起動中、停止中両方)

7. コンテナに入らずにコンテナ内の MySQL にアクセスする

「docker exec」を使わず、ターミナルからコンテナ内の MySQL にアクセスしてみます。

「mysql -h 127.0.0.1 -P 13306 -u root -p」のコマンドでポート 13306 番ポートの MySQL を指定してログインします。

% mysql -h 127.0.0.1 -P 13306 -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.33 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)

mysql> 

こんな感じでアクセスできました。

8. データベースを作成する

「db_in_container」というデータベースを作ってみます。

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.01 sec)

mysql> create database db_in_container;
Query OK, 1 row affected (0.01 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| db_in_container    |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.01 sec)

無事作れました。

9. DBeaver で接続してみる

Github で開発ローカル → リモートリポジトリ → 本番ローカルに反映させる手順

リモートリポジトリの作成

GitHub のサイトでリポジトリを作成し、git@github.com:〜 か https://github.com/〜 の接続先を取得します。

開発用ローカルリポジトリの作成

GitHub リポジトリにしたいディレクトリへ移動して下記を実行します。

ローカルのディレクトリを GitHub リポジトリとして登録、参照するリモートリポジトリを登録します。

% git init
% git remote add origin https://github.com/ユーザー名/リポジトリ名.git

開発ローカル → リモートリポジトリ

ファイルをローカルリポジトリのステージングに反映、ファイルの状態をコミット、そしてリモートリポジトリに push しています。

% git add .
% git commit -m "Create project."
% git push -u origin master

リモートリポジトリ → 本番ローカル

Git がインストールされている本番環境のルートディレクトリで下記を行います。(初回のみ)

$ git init
$ git remote add origin https://github.com/ユーザー名/リポジトリ名.git

そして pull でリモートリポジトリの更新を本番環境へ反映します。

$ git pull origin master

Google Foobar Challenge を終えて

Google Foobar Challenge を知っていますか?

この記事に来た方は実際に発動したりどこかで見聞きしたかで調べているかと思います。僕はある土曜日の朝、突然 Google 検索の画面がおかしな動きをして初めてその存在を知りました。

Google Foobar Challenge とは

Google Foobar Challenge は Google がプログラミング関連の調べごとをしている人に「ちょっと遊ぼうよ」的な感じで発動するプログラミング問題(ゲーム?)のことです。

「宇宙空間に浮かぶラムダ司令官の悪の組織から、囚われの身になっているウサギちゃんたちを救う」という、どこのAIが考えたのか分からないストーリーを題材に、黒バックのコマンド入力画面の様なUIでプログラミングの問題を解いていきます。

途中まで解くと Google 社に自分の連絡先を送れたりもするので、本気で取り組む価値はあると思いますし、何より面白いです!好奇心旺盛な人にはたまらない時間になると思います。

そして僕はこの度めでたく全問クリア出来たので、記録として記事を書いてます。というか Foobar Challenge は全編英語というのもあって日本語の記事が全然見当たらなかったので書いてます。

問題とコードは GitHub に置いています

下記リポジトリに各問題と自分が作成したコードを載せていますので参考にしてみてください。*参考にしたくない方も Star 等いただけると助かります。。笑

https://github.com/kttyo/google-foobar-challenge

挑戦するには?

URL はこれですが、ページに飛んだところで誰でも挑戦できるわけではなく、Google 検索をしている時に自然発生するのをただただ心待ちにするか、知り合いが頑張って取得した招待用リンクをもらうかの2つしかありません。

必要なスキル

必要なスキルとしては、まずプログラミングの基本的な知識と英語力。そして途中からはアルゴリズムの知識、そして終盤は数学の知識も必要になります。ちなみに言語は Java と Python が使えます。僕は Python で解いていきました。

もちろん初めから全部揃っていたら最高だとは思いますが、後述の通り回答に結構時間をかけられるので必要に応じて調べていくのでも問題ないと思います。

プログラミングの知識よりも発想力&想像力

プログラミングに関しては特に言語に精通している必要はないです。僕は Python 初心者で、そもそも今回 Google Foobar Challenge が発動した時も try-catch 文の書き方をググってたぐらいです。

それよりも問題を解決する方法を思いつく発想力や、色々な処理パターンを思い浮かべる想像力の方が大切だと思います。あとはそれを元に考えたロジックをコードに落とし込めれば大丈夫で、オブジェクト指向的な知識も全く必要ないです。

各レベルの内容

今後変更があるかもしれませんが、2021年1月時点ではレベル1〜5の全9問で構成されています。レベル1は本当に基本的な内容ですが、徐々に難易度が上がりレベル5はもう訳がわかりませんでした。問題自体は多分いくつかパターンがあると思うので、使うアルゴリズムの種類などは人によって変わると思います!

各レベルの主な内容を紹介します。

レベル1

まずは肩慣らしのような感じ。変数やループ等、プログラミングの基礎の基礎がわかっていれば大丈夫です。

  • 問題数:1 問
  • 制限時間:48 時間

こんな感じで進捗を確認できます。

レベル2

レベル 1 はプログラミング経験者なら誰でも解けますが、レベル 2 からはちゃんと頭を使う問題になります。このレベルをクリアすると友達に送る用のリンクを一つもらえます。

  • 問題数:2 問
  • 制限時間:各問題 168 時間(1 週間)
  • クリアすると:招待用リンクがもらえる(1 度目)

レベル3

経験者の書き込みをいくつか見ると、Google の採用面接で出されるコーディング問題はこの辺りのレベルだとのこと。このレベルをクリアすると Google に連絡先を送ることもできるので、ここまではがんばりたいですね!

  • 問題数:3 問
  • 制限時間:各問題 168 時間(1 週間)
  • クリアすると:Google に連絡先を送れる

迷路の最短距離の問題など、後になって考えてみればアルゴリズムの知識があればよかったなーと思いますが、想像力一本勝負でがんばりました。この辺りからは処理速度を気にしたプログラムを書かないと普通にタイムアウトでテストケースが Fail になります。

小休止(お勉強タイム)

このタイミングでアルゴリズムの知識を身に付けないとまずいと思い、下記の二冊をポチりました。

まず一つ目

アルゴリズム図鑑 絵で見てわかる26のアルゴリズム

こちらはプログラミング言語関係なくメジャーなアルゴリズムの概要をイラストでひたすら分かりやすく教えてくれます。「アルゴリズムとはそもそもなんぞや」だった僕にとっては最高の入り口でした。

そして二つ目

Pythonではじめるアルゴリズム入門 伝統的なアルゴリズムで学ぶ定石と計算量

こちらは、一冊目の「アルゴリズム図鑑」とほぼ同じアルゴリズムを、Python のコードではどう表現するのかを教えてくれます。最高です。

問題と問題の間はいくら時間を開けても良いので、心の準備ができるまで備えると良いと思います!

レベル4

ここからはいよいよ発想力だけではどうにもならなくなり、アルゴリズムが必須になります。そして流石の Google 先生。アルゴリズムを当てはめて終わりではなく、さらに一捻り二捻りする必要があります。

  • 問題数:2 問
  • 制限時間:各問題 360 時間(15 日間)
  • クリアすると:招待用のリンクがもらえる(2 度目)

1 問目は「せっかく学んだし」と思って幅優先探索を使ってみたんですけど処理に時間がかかりすぎたので結局別の方法に切り替えました。2 問目は最短経路を求めるベルマンフォード法を応用したイメージでした。

レベル5

いよいよ最終問題。そして流石の最終問題。もはやただの数学でした。

数学の知識はほぼゼロでしたが調べた内容を書くとするなら、バーンサイドの補題(Burnside’s Lemma)、ポリアの数え上げ問題(Polya’s Enumeration Theorem)、ユークリッドの互除法、順列と組み合わせ(Permutations & Combinations)、などなど。。。

  • 問題数:1 問
  • 制限時間:528時間(22 日間)

この記事(15.5 Applications of Pólya's Enumeration Formula)を読んでみたり、後述の動画をみたり。とりあえず何か掴めないか模索しました。

人によってはレベル4が一番難しいと感じる人もいるみたいですが、僕にとっては圧倒的にレベル5が一番難しかったです。途中ちょっと心が折れそうになりましたが、12日間かかってやっとクリア。

最終問題で参考にした動画

ポイント

  • 「verify solution」が数秒で終わらない場合はパフォーマンス的なところで Fail してる可能性があります。書いたコードの計算数をイメージすることが大事です。
  • 問題の細かい条件は読み取るのが難しいので、早めにコードを書いて「verify solution」でテストしながら察していくことが大事だと思います。
  • とはいえ問題はなるべく正確に理解する様注意した方がいいです(当然ですが)。自分の場合レベル 4 でコードをしばらく書いた後でどうにも上手くいかないのでよーく問題を読み直してみたら自分で理解していた条件と実際が異なることが分かり、ほぼ丸ごとロジックを考え直しました…。
  • ちっちゃいホワイトボードとかあると便利です。iPadとかでも良いのかな?その場で書いて消してを繰り返せるので。
  • 参考までに、僕の回答は大体数十行で一番多くても 200 行未満で済んだので(あくまで Python の場合)、もしもやたらと処理が多くなっている場合は考えすぎているかもしれません。

まとめ

コロナでのステイホーム中に突然始まった Google Foobar Challenge。思いの外夢中になってとてもエキサイティングな数週間になりました。初めは全てクリアできるとも思っていなかったので、最終的には自信を得られたことが一番良かったことかもしれません。

皆さんももし挑戦する機会があったら週末など出来るだけ時間を取れる期間を見つけて、思い切り没頭してみてください。きっと素敵なおうち時間になると思います。

繰り返しになりますが、下記リポジトリに各問題と自分が作成したコードを載せていますので、参考にしたくない方も Star 等いただけると助かります。。笑

https://github.com/kttyo/google-foobar-challenge