专栏首页0x0001基于 Docker 的 Flarum 轻论坛部署方案

基于 Docker 的 Flarum 轻论坛部署方案

Flarum 是一个简洁的轻论坛程序,交互体验做的十分不错,也有良好的插件扩展机制。接触过的人可能知道,它目前还在 beta,在功能更新和迭代方面不算稳定,部署、修改与定制功能更是一件麻烦的事情。

在 2018 年,我基于它构建了 0xFFFF 社区。经过两年的不断推翻与修改,慢慢沉淀下了一套适合持续迭代的 Flarum 部署与开发迭代方案。

这里主要介绍 Flarum 在服务器和本地开发环境的部署方案。本文假定读者对 Linux 命令行操作、Docker 与 Docker Compose 有基本的了解。相关文件均已开源在 GitHub: zgq354/flarum-docker-env

Why Docker

在 Linux 折腾 LAMP/LNMP 的同学可能经常被各种环境配置的细节问题折磨,诸如 Nginx 配置、“伪静态”(URL Rewrite)、各种文件权限、所有者问题等等。好不容易配置好了,过一两个月可能已经完全忘记,在未来需要修改或更新之时,如西西弗斯受罚一般,重重复复做着相似的事。

基于 Docker,只需要一系列配置文件,就可以从各种各样的针对手动配置解放出来,通过 Git 管理配置的历史版本。可以随时切换环境配置,而不担心因时间的流逝忘记当初是怎么搞的。

接下来会介绍这个方案的细节,若只想把项目跑起来,可以直接跳到本文的 “使用” 小节。

镜像的选择

官方 安装文档 对环境的要求:

  • Apache (with mod_rewrite enabled) or Nginx
  • PHP 7.2.9+ with the following extensions: curl, dom, gd, json, mbstring, openssl, pdo_mysql, tokenizer, zip
  • MySQL 5.6+ or MariaDB 10.0.5+
  • SSH (command-line) access to run Composer

本质上来说是一个基于 LAMP/LNMP 架构的应用,所以我们只需要准备三个东西:Web 服务器、PHP 和 数据库,这里用到三个应用容器:

  • Nginx:Web 服务器,负责输出静态文件、将需要 PHP 处理的请求通过 FastCGI 协议 转发给 PHP-FPM
  • PHP-FPM:PHP 的 FastCGI 进程管理器,接收 Web 服务器的 FastCGI 请求,执行对应的 PHP 脚本
  • MySQL 5.7:网站专用数据库

再考虑到数据库管理、还有 HTTPS 证书签发的问题,我们再加上这俩:

在申请到 Let's Encrypt 证书之前,为了完整提供 HTTPS,Nginx 需默认提供使用自签名证书的选项。PHP-FPM 需要安装各种 PHP 扩展,所以 Nginx 与 PHP-FPM 会在基础镜像之上再做一些自定义修改。

为了开发迭代的方便,我们把网站主体文件放在宿主机,然后通过 Volume 的方式绑定 Docker 容器,这一点接下来会提到。

目录结构

Docker 容器在设计用途上不考虑状态的持久化,每次更新配置,都会通过重新创建新的容器替换原本的容器,原本容器会被销毁。为了数据的持久化,Docker 提供了 Volume 的机制,将 Volume 挂载到容器文件系统的指定路径,写入的数据会通过 Volume 保留。我们把宿主机的特定路径作为 Volume,实现容器内目录和宿主机的映射。需持久化的有:

  1. 数据库数据的文件(MySQL 一般在 /var/lib/mysql
  2. Nginx 的 Web 访问日志、配置文件
  3. 证书签发相关文件

本着 Docker 容器产生的文件都归于一处的原则,我们把相关的文件都归在宿主机下的 ./data 之下。网站主体代码也通过 Volume 挂载,这里放在 ./www 之下,整体目录结构安排如下:

.
├── data
│   ├── db-data           # MySQL 数据文件
│   ├── logs              # 日志文件
│   └── ssl               # ssl 证书相关配置
├── docker-compose.yml
├── nginx                 # Nginx 镜像相关文件
│   ├── Dockerfile
│   ├── conf              # Nginx 配置
│   └── start.sh
├── php-fpm               # php-fpm 镜像相关
│   └── Dockerfile
└── www                   # 站点相关文件

各容器配置

本节将展开介绍各个容器的配置细节,包括 MySQL、Nginx、php-fpm、phpMyAdmin 以及 acme.sh 的证书申请机制。

MySQL

MySQL 容器直接用官方镜像,通过 .env 设置环境变量,加载 MySQL 初始化的连接密码等。

services:
  database:
    image: mysql:5.7
    restart: always
    container_name: site-db
    expose:
      - 3306
    environment:
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASS}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
    volumes:
      - ./data/db-data:/var/lib/mysql

Nginx

Nginx 采用了基于 alpine 的镜像,体积较小。在配置上,大体参考了 Nginx 在发行版中的目录结构,并参考了 Debian 的 nginx 包的目录安排,再考虑 Nginx 镜像内部的结构,绑定了三个路径。

 - ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf
 - ./nginx/conf/conf.d:/etc/nginx/conf.d
 - ./nginx/conf/snippets:/etc/nginx/snippets

各个路径的作用:

  • nginx.conf:覆盖原始的配置文件
  • conf.d:Nginx HTTP 服务与站点相关的配置,会被 nginx.conf include 进去
  • snippets:各种代码段,这里放了一个 SSL 相关的配置

对于 Web 站点的文件,我们把容器内部 /www/flarum 绑定到本地的 ./www/flarum

nginx.conf 参考:

user nginx;
worker_processes 1;
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 application/octet-stream;
    log_format main '$http_x_forwarded_for - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';

    access_log /var/log/nginx/access.log;

    sendfile on;
    keepalive_timeout 65;
    client_max_body_size 20M;

    include conf.d/*.conf;
}

SSL 相关参数,毕竟你不是安全人员,自己配置并不稳妥,所以还是用 Mozilla 提供的工具 生成吧。

snippets/ssl-params.conf 参考:

# generated 2020-05-21, Mozilla Guideline v5.4, nginx 1.17.7, OpenSSL 1.1.1d, intermediate configuration, no HSTS, no OCSP
# https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=intermediate&openssl=1.1.1d&hsts=false&ocsp=false&guideline=5.4

ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions
ssl_session_tickets off;

# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam /etc/ssl/dhparam.pem;

# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

其中有一个比较关键的 DH 参数,也用 Mozilla 推荐的,我们把这个逻辑加到 Dockerfile。

curl https://ssl-config.mozilla.org/ffdhe2048.txt > /etc/ssl/dhparam.pem

站点相关配置,SSL 证书默认放在 /etc/ssl/certs/ 的以域名命名的目录下,参考以下配置,这里证书相关的参数,引用了 snippets/ssl-params.conf

conf.d/flarum.conf 的配置参考如下:

server {
    listen 80;
    listen 443 ssl http2;

    ssl_certificate /etc/ssl/certs/example.com/full.pem;
    ssl_certificate_key /etc/ssl/certs/example.com/key.pem;
    include snippets/ssl-params.conf;

    # should be changed
    server_name example.com;

    root /www/flarum/public;
    index index.php index.html;

    server_tokens off;
    access_log /var/log/nginx/flarum-access.log;
    error_log /var/log/nginx/flarum-error.log;

    # for let's encrypt
    location /.well-known/ {
        alias /.well-known/;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php-fpm-service:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    # Pass requests that don't refer directly to files in the filesystem to index.php
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # The following directives are based on best practices from H5BP Nginx Server Configs
    # https://github.com/h5bp/server-configs-nginx

    # Expire rules for static content
    location ~* \.(?:manifest|appcache|html?|xml|json)$ {
        add_header Cache-Control "max-age=0";
    }

    location ~* \.(?:rss|atom)$ {
        add_header Cache-Control "max-age=3600";
    }

    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|mp4|ogg|ogv|webm|htc)$ {
        add_header Cache-Control "max-age=2592000";
        access_log off;
    }

    location ~* \.(?:css|js)$ {
        add_header Cache-Control "max-age=31536000";
        access_log off;
    }

    location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ {
        add_header Cache-Control "max-age=2592000";
        access_log off;
    }

    # Gzip compression
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_proxied any;
    gzip_vary on;
    gzip_types
        application/atom+xml
        application/javascript
        application/json
        application/ld+json
        application/manifest+json
        application/rss+xml
        application/vnd.geo+json
        application/vnd.ms-fontobject
        application/vnd.api+json
        application/x-font-ttf
        application/x-web-app-manifest+json
        application/xhtml+xml
        application/xml
        font/opentype
        image/bmp
        image/svg+xml
        image/x-icon
        text/cache-manifest
        text/css
        text/plain
        text/vcard
        text/vnd.rim.location.xloc
        text/vtt
        text/x-component
        text/x-cross-domain-policy;
}

在 Dockerfile 的配置上,为了避免进程无法停止、僵尸进程等问题,容器加入 dumb-init 作为入口程序。

考虑到证书可能不存在的情况,修改启动脚本加入检测证书是否存在的机制。若证书不存在,就调用 OpenSSL 自签一个证书,避免启动失败(但这个证书也不会被客户端信任),具体的域名则通过环境变量传入。

启动脚本 start.sh:

#!/bin/sh -
CERT_DOMAIN=${DOMAIN:-example.com}

if [[ ! -e /etc/ssl/certs/$CERT_DOMAIN/key.pem ]]; then
    mkdir -p /etc/ssl/certs/$CERT_DOMAIN
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/certs/$CERT_DOMAIN/key.pem -out /etc/ssl/certs/$CERT_DOMAIN/full.pem \
      -subj "/C=CN/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=$CERT_DOMAIN"
fi

nginx -g "daemon off;"

也就是说,这里预置了一个自签名的 ssl 证书,若不向 Let's Encrypt 申请证书,你对这一系列容器的 HTTPS 请求是不受浏览器信任的。

php-fpm

php-fpm 镜像较为简单,直接配置 Dockerfile,在 php:7.4-fpm-alpine 镜像的基础上再加上 gdpdo_mysqlexif 扩展(缺啥补啥)。

还需要考虑 Docker 内用户的 UID 与宿主机用户的 UID 的对应关系,涉及到写入权限的问题。(Docker Volume 的文件所有者的 UID 与宿主机是同步的,可能同一 UID 对应不同的用户名)。

Dockerfile 如下:

FROM php:7.4-fpm-alpine

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US.UTF-8
ENV LC_ALL=en_US.UTF-8

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && \
    echo "Asia/Shanghai" > /etc/timezone

RUN apk add \
        freetype \
        freetype-dev \
        libpng \
        libpng-dev \
        oniguruma-dev \
        libjpeg-turbo \
        libjpeg-turbo-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd \
    && apk del \
        freetype-dev \
        libpng-dev \
        libjpeg-turbo-dev

RUN docker-php-ext-install pdo_mysql opcache exif

RUN apk --no-cache add shadow \
    && usermod -u 1000 www-data \
    && groupmod -g 1000 www-data \
    && rm /var/cache/apk/*

ENTRYPOINT ["docker-php-entrypoint"]

STOPSIGNAL SIGQUIT

EXPOSE 9000
CMD ["php-fpm"]

phpMyAdmin

引入 phpmyadmin/phpmyadmin:fpm-alpine 镜像,镜像内的文件都在 /var/www/html,这里我们将 phpMyAdmin 内的 /var/www/html 通过 Volume 映射到 Nginx 的 /www/pma 目录下,这样 Nginx 遇到静态文件请求可以直接通过 /www/pma 访问到,遇到动态文件请求时,则转发给 phpMyAdmin 的容器。

location ~ \.php$ {
    try_files $uri /index.php$is_args$args;
    fastcgi_pass pma-service:9000;
    fastcgi_hide_header X-Powered-By;

    # 传给 phpMyAdmin 容器的 php-fpm 的路径 (/var/www/html)
    fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
    include fastcgi_params;
}

如上,在写处理 .php 后缀的 location 的转发配置时需要留意 /www/pma/var/www/html 的差异。这时候我们需要引入 fastcgi_params 文件的预置参数,然后硬编码 SCRIPT_FILENAME

完整配置参考:conf.d/pma.conf

Let's Encrypt 证书申请

这里申请签发证书的部分,我们采用 acme.sh 的 Docker 方案,acme.sh 容器以守护进程的形式运行。

所有的证书相关文件都放在了容器的 /acme.sh 目录中,这里我们把它映射到 ./data/ssl/acmeout 里(具体参考 docker-compose.yml 的配置)。

Let's Encrypt 签发证书有多种验证方式,acme.sh 均有封装。若不希望配置 DNS,可以使用 HTTP 的方式验证,本方案将 acme.sh 容器的 /.well-known 映射到了宿主机的 ./data/ssl/.well-known ,Nginx 把 ./data/ssl/.well-known 映射到了 /.well-known

通过 alias 指令实现访问验证文件的效果,如 flarum.conf 中的例子:

# for let's encrypt
location /.well-known/ {
    alias /.well-known/;
}

然后我们可以用 docker exec ,采用 HTTP 验证的途径来执行申请命令,稍等片刻即可申请好:

docker exec acme.sh --issue -d example.com -w /

申请好的证书需执行 acme.sh 的 deploy 部署到 nginx 中,用环境变量加载参数,同样以 example.com 为例。

docker exec \
  -e DEPLOY_DOCKER_CONTAINER_LABEL=sh.acme.autoload.domain=example.com \
  -e DEPLOY_DOCKER_CONTAINER_KEY_FILE=/etc/ssl/example.com/key.pem \
  -e DEPLOY_DOCKER_CONTAINER_CERT_FILE="/etc/ssl/example.com/cert.pem" \
  -e DEPLOY_DOCKER_CONTAINER_CA_FILE="/etc/ssl/example.com/ca.pem" \
  -e DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/etc/ssl/example.com/full.pem" \
  -e DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="kill 1" \
  acme.sh --deploy -d example.com --deploy-hook docker

然后 acme.sh 的守护进程将会定期检查,在证书快过期的时候自动执行续期逻辑。在执行完续期逻辑后,会在标记了 sh.acme.autoload.domain=example.com 的标签的 nginx 容器执行 kill 1,干掉这个容器的进程,自动重启容器,实现证书的重新加载。

完整 docker-compose.yaml

version: "3.6"

services:
  database:
    image: mysql:5.7
    restart: always
    container_name: site-db
    expose:
      - 3306
    environment:
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASS}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
    volumes:
      - ./data/db-data:/var/lib/mysql

  nginx:
    image: nginx-flarum
    build:
      context: ./nginx
      args: 
        - DOMAIN=${DOMAIN}
    container_name: site-nginx
    restart: always
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./data/logs:/var/log/nginx
      - ./data/ssl/.well-known:/.well-known
      - ./data/ssl/certs:/etc/ssl/certs
      - ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/conf/conf.d:/etc/nginx/conf.d
      - ./nginx/conf/snippets:/etc/nginx/snippets
      - ./www/flarum:/www/flarum
      - pma-root:/www/pma # phpMyAdmin
    environment:
      - DOMAIN=${DOMAIN}
    extra_hosts:
      - "localhost:127.0.0.1"
    labels:
      - sh.acme.autoload.domain=${DOMAIN}
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider --proxy off http://localhost/get-health || exit 1"]
      interval: 5s
      retries: 12
    logging:
      driver: "json-file"
      options:
        max-size: "100m"

  acme.sh:
    image: neilpang/acme.sh
    container_name: acme.sh
    command: daemon
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data/ssl/acmeout:/acme.sh
      - ./data/ssl/.well-known:/.well-known
    environment:
      - DEPLOY_DOCKER_CONTAINER_LABEL=sh.acme.autoload.domain=${DOMAIN}
      - DEPLOY_DOCKER_CONTAINER_KEY_FILE=/etc/ssl/certs/${DOMAIN}/key.pem
      - DEPLOY_DOCKER_CONTAINER_CERT_FILE="/etc/ssl/certs/${DOMAIN}/cert.pem"
      - DEPLOY_DOCKER_CONTAINER_CA_FILE="/etc/ssl/certs/${DOMAIN}/ca.pem"
      - DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/etc/ssl/certs/${DOMAIN}/full.pem"
      - DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="kill 1"

  php-fpm-service:
    image: php-fpm-flarum
    build: ./php-fpm
    container_name: site-php-fpm
    restart: always
    expose:
      - 9000
    volumes:
      - ./data/logs:/var/log
      - ./www/flarum:/www/flarum
    healthcheck:
      test: ["CMD-SHELL", "pidof php-fpm"]
      interval: 5s
      retries: 12
    logging:
      driver: "json-file"
      options:
        max-size: "100m"

  pma-service:
    image: phpmyadmin/phpmyadmin:fpm-alpine
    container_name: site-pma
    restart: always
    environment: 
      - PMA_HOST=site-db
    volumes:
      - pma-root:/var/www/html

volumes: 
  pma-root:

使用

创建 Flarum 文件

在开始使用本方案的环境之前,你需要在宿主机本地先把 Flarum 站点的文件准备好。

首先安装 PHP 包管理器 Composer:

wget -O composer-setup.php https://getcomposer.org/installer
php composer-setup.php --install-dir=bin --filename=composer

设置国内镜像(避免加载过慢,这里可以用阿里云的镜像)

composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

这里我们假设站点文件都放在 /var/www/flarum 中(假设你有 /var/www 的所有者,若不是,可 sudo chown <你的用户名>:<你的用户名> /var/www ),执行安装。

cd /var/www/
mkdir flarum && cd flarum
composer create-project flarum/flarum . --stability=beta

等 composer 跑完,安装 Flarum 需要的文件已经准备好了。

部署

无论是线上部署还是本地开发,套路都很一致。

  1. 首先在宿主机安装 Docker CEDocker Compose
  2. 克隆项目代码(你也可以用这个 Template 创建自己的项目,再克隆,这样可以自己更新)
cd /var/www
git clone https://github.com/zgq354/flarum-docker-env.git
cd flarum-docker-env
  1. 创建符号链接(若不想创建符号链接,也可以在 www/flarum 里面执行 composer create-project flarum/flarum . --stability=beta 加入安装文件)
ln -s /var/www/flarum www/flarum
  1. 创建环境变量配置 .env 文件,可参考 .env-example
cp .env-example .env
vim .env

DB_PASS,DB_ROOT_PASS 需改成实际想要的密码,Flarum:

DOMAIN=example.com

DB_NAME=flarum_db
DB_USER=flarum_db_user
DB_PASS=xxxxx
DB_ROOT_PASS=xxxxx
  1. 修改 nginx 配置,把 pma.confflarum.conf 里面的 server_name 配置为对应的域名。
  2. 启动
docker-compose up -d

然后把域名解析至服务器所在 IP,就能打开安装界面了,安装时需注意,MySQL Host 应为 MySQL Docker 容器对应的 site-db

没有现成的域名?没关系,你可以参考接下来的本地环境的方案来将任意域名指向服务器的 IP。

完成以上步骤后,若需要跑在线上环境,还需按照前文 acme.sh 的部分的方式,申请 Let's Encrypt 认证的 ssl 证书。

本地环境

本地环境开发,推荐使用 LightProxy 作为开发环境调试的代理工具,LightProxy 是开源抓包工具 whistle 的桌面版封装,可以用类似 hosts 的语法指定域名和 IP 的对应关系。

example.com 127.0.0.1

若在本地部署,按 127.0.0.1 的方式就可以在本地访问,开发环境与生产环境保持同一域名。

限于篇幅,关于本地开发环境的搭建、调试、版本管理等方案,我们下一篇文章再具体介绍。

参考:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 基于 VSCode 与 Docker Desktop 配置 Flarum 本地开发环境

    上篇文章 我们写了 Flarum 二次开发项目的规划,这里也分享一下本地开发与调试 Flarum 的配置的方案,助你快速搭起 Flarum 的本地开发调试环境。

    zgq354
  • Ubuntu 16.04 编译安装 PHP 7.2

    上一篇文章 Linux 下 Nginx + PHP 环境的配置 扯了那么多复杂的概念,现在让我来说一说怎么把它用在实践之中。

    zgq354
  • 百度音乐api接口

    最近正在研究百度音乐的API,上网搜了下,不外乎就是直接抓网页或者是已经用烂的的widget的API:

    zgq354
  • 避坑!用Docker搞定PHP开发环境搭建

    因为最近换电脑的缘故需要重新部署一下本地的开发环境,之前采用的虚拟机方案,占用磁盘空间实在是太大,心疼SSD那么点地方,所以这次采用了Docker的方案来部署。...

    猿哥
  • 搭建一个google镜像站

    不知道为什么最近对作镜像网站特别感兴趣,为了改变自己习惯用baidu的坏习惯,我觉得我还是搭建一个谷歌的镜像网站吧,毕竟,百度是垃圾无道德的一家公司

    bboysoul
  • 关于Linux服务器部署laravel项目

    hedeqiang
  • linux学习第四十六篇:Nginx防盗链,Nginx访问控制,Nginx解析php相关配置,Nginx代理

    Nginx防盗链 vim /usr/local/nginx/conf/vhost/test.com.conf 配置如下,可以和上面的配置结合起来: loca...

    用户1215343
  • 【实例】R语言如何做银行财务数据分析?

    搜集银行业上市公司的财务数据分析股票价格的财务影响因素,观测流动比率、净资产负债比率、资产固定资产比率、每股收益、净利润、增长率、股价和公布时间等数据。首...

    机器学习AI算法工程
  • 容器生态系统

    tanmx
  • Docker构建

    ##构建三个docker,php、nginx、mysql三个镜像 ###1,先从docker仓库里面拉取centos镜像,和mysql镜像

    张琳兮

扫码关注云+社区

领取腾讯云代金券