乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Docker在PHP項目開發(fā)環(huán)境中的應用

       千秋鶴 2016-08-19

      環(huán)境部署是所有團隊都必須面對的問題,隨著系統(tǒng)越來越大,依賴的服務也越來越多,比如我們目前的一個項目就會用到:

      • Web服務器:Nginx
      • Web程序:PHP + Node
      • 數(shù)據(jù)庫:MySQL
      • 搜索引擎:ElasticSearch
      • 隊列服務:Gearman
      • 緩存服務:Redis + Memcache
      • 前端構(gòu)建工具:npm + bower + gulp
      • PHP CLI工具:Composer + PHPUnit

      因此團隊的開發(fā)環(huán)境部署隨之暴露出若干問題:

      1. 依賴服務很多,本地搭建一套環(huán)境成本越來越高,初級人員很難解決環(huán)境部署中的一些問題
      2. 服務的版本差異及OS的差異都可能導致線上環(huán)境BUG
      3. 項目引入新的服務時所有人的環(huán)境需要重新配置

      對于問題1,可以用Vagrant這樣的基于虛擬機的項目來解決,團隊成員共享一套開發(fā)環(huán)境鏡像。對于問題2,可以引入類似PHPBrew這樣的多版本PHP管理工具來解決。但兩者都不能很好地解決問題3,因為虛擬機鏡像沒有版本管理的概念,當多人維護一個鏡像時,很容易出現(xiàn)配置遺漏或者沖突,一個很大的鏡像傳輸起來也不方便。

      Docker的出現(xiàn)讓上面的問題有了更好的解決方案,雖然個人對于Docker大規(guī)模應用到生產(chǎn)環(huán)境還持謹慎態(tài)度,但如果僅僅考慮測試及開發(fā),私以為Docker的容器化理念已經(jīng)是能真正解決環(huán)境部署問題的銀彈了。

      下面介紹Docker構(gòu)建PHP項目開發(fā)環(huán)境過程中的演進,本文中假設你的操作系統(tǒng)為Linux,已經(jīng)安裝了Docker,并且已經(jīng)了解Docker是什么,以及Docker命令行的基礎使用,如果沒有這些背景知識建議先自行了解。

      Hello World

      首先還是從一個PHP在Docker容器下的Hello World實例開始。我們準備這樣一個PHP文件index.php

      <?php
      echo "PHP in Docker";
      

      然后在同目錄下創(chuàng)建文本文件并命名為Dockerfile,內(nèi)容為:

      # 從官方PHP鏡像構(gòu)建
      FROM       php
      
      # 將index.php復制到容器內(nèi)的/var/www目錄下
      ADD        index.php /var/www/
      
      # 對外暴露8080端口
      EXPOSE     8080
      
      # 設置容器默認工作目錄為/var/www
      WORKDIR    /var/www/
      
      # 容器運行后默認執(zhí)行的指令
      ENTRYPOINT ["php", "-S", "0.0.0.0:8080"]
      

      構(gòu)建這個容器:

      docker build -t allovince/php-helloworld .
      

      運行這個容器

      docker run -d -p 8080:8080 allovince/php-helloworld
      

      查看結(jié)果:

      curl localhost:8080
      PHP in Docker
      

      這樣我們就創(chuàng)建了一個用于演示PHP程序的Docker容器,任何安裝過Docker的機器都可以運行這個容器獲得同樣的結(jié)果。而任何有上面的php文件和Dockerfile的人都可以構(gòu)建出相同的容器,從而完全消除了不同環(huán)境,不同版本可能引起的各種問題。

      想象一下程序進一步復雜,我們應該如何擴展呢,很直接的想法是繼續(xù)在容器內(nèi)安裝其他用到的服務,并將所有服務運行起來,那么我們的Dockerfile很可能發(fā)展成這個樣子:

      FROM       php
      ADD        index.php /var/www/
      
      # 安裝更多服務
      RUN        apt-get install -y            mysql-server            nginx            php5-fpm            php5-mysql
      
      # 編寫一個啟動腳本啟動所有服務
      ENTRYPOINT ["/opt/bin/php-nginx-mysql-start.sh"]
      

      雖然我們通過Docker構(gòu)建了一個開發(fā)環(huán)境,但覺不覺得有些似曾相識呢。沒錯,其實這種做法和制作一個虛擬機鏡像是差不多的,這種方式存在幾個問題:

      • 如果需要驗證某個服務的不同版本,比如測試PHP5.3/5.4/5.5/5.6,就必須準備4個鏡像,但其實每個鏡像只有很小的差異。
      • 如果開始新的項目,那么容器內(nèi)安裝的服務會不斷膨脹,最終無法弄清楚哪個服務是屬于哪個項目的。

      使用單一進程容器

      上面這種將所有服務放在一個容器內(nèi)的模式有個形象的非官方稱呼:Fat Container。與之相對的是將服務分拆到容器的模式。從Docker的設計可以看到,構(gòu)建鏡像的過程中可以指定唯一一個容器啟動的指令,因此Docker天然適合一個容器只運行一種服務,而這也是官方更推崇的。

      分拆服務遇到的第一個問題就是,我們每一個服務的基礎鏡像從哪里來?這里有兩個選項:

      選項一、 統(tǒng)一從標準的OS鏡像擴展,比如下面分別是Nginx和MySQL鏡像

      FROM ubuntu:14.04
      RUN  apt-get update -y && apt-get install -y nginx
      
      FROM ubuntu:14.04
      RUN  apt-get update -y && apt-get install -y mysql
      

      這種方式的優(yōu)點在于所有服務可以有一個統(tǒng)一的基礎鏡像,對鏡像進行擴展和修改時可以使用同樣的方式,比如選擇了ubuntu,就可以使用apt-get指令安裝服務。

      問題在于大量的服務需要自己維護,特別是有時候需要某個服務的不同版本時,往往需要直接編譯源碼,調(diào)試維護成本都很高。

      選項二、 直接從Docker Hub繼承官方鏡像,下面同樣是Nginx和MySQL鏡像

      FROM nginx:1.9.0
      
      FROM mysql:5.6
      

      Docker Hub可以看做是Docker的Github,Docker官方已經(jīng)準備好了大量常用服務的鏡像,同時也有非常多第三方提交的鏡像。甚至可以基于Docker-Registry項目在短時間內(nèi)自己搭建一個私有的Docker Hub。

      基于某個服務的官方鏡像去構(gòu)建鏡像,有非常豐富的選擇,并且可以以很小的代價切換服務的版本。這種方式的問題在于官方鏡像的構(gòu)建方式多種多樣,進行擴展時需要先了解原鏡像的Dockerfile。

      出于讓服務搭建更靈活的考慮,我們選擇后者構(gòu)建鏡像。

      為了分拆服務,現(xiàn)在我們的目錄變?yōu)槿缦滤窘Y(jié)構(gòu):

      ~/Dockerfiles
      ├── mysql
      │   └── Dockerfile
      ├── nginx
      │   ├── Dockerfile
      │   ├── nginx.conf
      │   └── sites-enabled
      │       ├── default.conf
      │       └── evaengine.conf
      ├── php
      │   ├── Dockerfile
      │   ├── composer.phar
      │   ├── php-fpm.conf
      │   ├── php.ini
      │   ├── redis.tgz
      └── redis
          └── Dockerfile
      

      即為每個服務創(chuàng)建單獨文件夾,并在每個服務文件夾下放一個Dockerfile。

      MySQL容器

      MySQL繼承自官方的MySQL5.6鏡像,Dockerfile僅有一行,無需做任何額外處理,因為普通需求官方都已經(jīng)在鏡像中實現(xiàn)了,因此Dockerfile的內(nèi)容為:

      FROM mysql:5.6
      

      在項目根目錄下運行

      docker build -t eva/mysql ./mysql
      

      會自動下載并構(gòu)建鏡像,這里我們將其命名為eva/mysql。

      由于容器運行結(jié)束時會丟棄所有數(shù)據(jù)庫數(shù)據(jù),為了不用每次都要導入數(shù)據(jù),我們將采用掛載的方式持久化MySQL數(shù)據(jù)庫,官方鏡像默認將數(shù)據(jù)庫存放在/var/lib/mysql,同時要求容器運行時必須通過環(huán)境變量設置一個管理員密碼,因此可以使用以下指令運行容器:

      docker run -p 3306:3306 -v ~/opt/data/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -it eva/mysql
      

      通過上面的指令,我們將本地的3306端口綁定到容器的3306端口,將容器內(nèi)的數(shù)據(jù)庫持久化到本地的~/opt/data/mysql,并且為MySQL設置了一個root密碼123456

      Nginx容器

      Nginx目錄下提前準備了Nginx配置文件nginx.conf以及項目的配置文件default.conf等。Dockerfile內(nèi)容為:

      FROM nginx:1.9
      
      ADD  nginx.conf      /etc/nginx/nginx.conf
      ADD  sites-enabled/*    /etc/nginx/conf.d/
      RUN  mkdir /opt/htdocs && mkdir /opt/log && mkdir /opt/log/nginx
      RUN  chown -R www-data.www-data /opt/htdocs /opt/log
      
      VOLUME ["/opt"]
      

      由于官方的Nginx1.9是基于Debian Jessie的,因此首先將準備好的配置文件復制到指定位置,替換鏡像內(nèi)的配置,這里按照個人習慣,約定/opt/htdocs目錄為Web服務器根目錄,/opt/log/nginx目錄為Nginx的Log目錄。

      同樣構(gòu)建一下鏡像

      docker build -t eva/nginx ./nginx
      

      并運行容器

      docker run -p 80:80 -v ~/opt:/opt -it eva/nginx
      

      注意我們將本地的80端口綁定到容器的80端口,并將本地的~/opt目錄掛載到容器的/opt目錄,這樣就可以將項目源代碼放在~/opt目錄下并通過容器訪問了。

      PHP容器

      PHP容器是最復雜的一個,因為在實際項目中,我們很可能需要單獨安裝一些PHP擴展,并用到一些命令行工具,這里我們以Redis擴展以及Composer來舉例。首先將項目需要的擴展等文件提前下載到php目錄下,這樣構(gòu)建時就可以從本地復制而無需每次通過網(wǎng)絡下載,大大加快鏡像構(gòu)建的速度:

      wget https:///composer.phar -O php/composer.phar
      wget https://pecl./get/redis-2.2.7.tgz -O php/redis.tgz
      

      php目錄下還準備好了php配置文件php.ini以及php-fpm.conf,基礎鏡像我們選擇的是PHP 5.6-FPM,這同樣是一個Debian Jessie鏡像。官方比較親切的在鏡像內(nèi)部準備了一個docker-php-ext-install指令,可以快速安裝如GD、PDO等常用擴展。所有支持的擴展名稱可以通過在容器內(nèi)運行docker-php-ext-install獲得。

      來看一下Dockerfile

      FROM php:5.6-fpm
      
      ADD php.ini    /usr/local/etc/php/php.ini
      ADD php-fpm.conf    /usr/local/etc/php-fpm.conf
      
      COPY redis.tgz /home/redis.tgz
      RUN docker-php-ext-install gd     && docker-php-ext-install pdo_mysql     && pecl install /home/redis.tgz && echo "extension=redis.so" > /usr/local/etc/php/conf.d/redis.ini
      ADD composer.phar /usr/local/bin/composer
      RUN chmod 755 /usr/local/bin/composer
      
      WORKDIR /opt
      RUN usermod -u 1000 www-data
      
      VOLUME ["/opt"]
      

      在構(gòu)建過程中做了這樣一些事情:

      1. 復制php和php-fpm配置文件到相應目錄
      2. 復制redis擴展源代碼到/home
      3. 通過docker-php-ext-install安裝GD和PDO擴展
      4. 通過pecl安裝Redis擴展
      5. 復制composer到鏡像作為全局指令

      按照個人習慣,仍然設置/opt目錄作為工作目錄。

      這里有一個細節(jié),在復制tar包文件時,使用的Docker指令是COPY而不是ADD,這是由于ADD指令會自動解壓tar文件。

      現(xiàn)在終于可以構(gòu)建+運行了:

      docker build -t eva/php ./php
      docker run -p 9000:9000 -v ~/opt:/opt -it eva/php
      

      在大多數(shù)情況下,Nginx和PHP所讀取的項目源代碼都是同一份,因此這里同樣掛載本地的~/opt目錄,并且綁定9000端口。

      PHP-CLI的實現(xiàn)

      php容器除了運行php-fpm外,還應該作為項目的php cli使用,這樣才能保證php版本、擴展以及配置文件保持一致。

      例如在容器內(nèi)運行Composer,可以通過下面的指令實現(xiàn):

      docker run -v $(pwd -P):/opt -it eva/php composer install --dev -vvv
      

      這樣在任意目錄下運行這行指令,等于動態(tài)將當前目錄掛載到容器的默認工作目錄并運行,這也是PHP容器指定工作目錄為/opt的原因。

      同理還可以實現(xiàn)phpunit、npm、gulp等命令行工具在容器內(nèi)運行。

      Redis容器

      為了方便演示,Redis僅僅作為緩存使用,沒有持久化需求,因此Dockerfile僅有一行

      FROM redis:3.0
      

      容器的連接

      上面已經(jīng)將原本在一個容器中運行的服務分拆到多個容器,每個容器只運行單一服務。這樣一來容器之間需要能互相通信。Docker容器間通訊的方法有兩種,一種是像上文這樣將容器端口綁定到一個本地端口,通過端口通訊。另一種則是通過Docker提供的Linking功能,在開發(fā)環(huán)境下,通過Linking通信更加靈活,也能避免端口占用引起的一些問題,比如可以通過下面的方式將Nginx和PHP鏈接起來:

      docker run -p 9000:9000 -v ~/opt:/opt --name php -it eva/php
      docker run -p 80:80 -v ~/opt:/opt -it --link php:php eva/nginx
      

      在一般的PHP項目中,Nginx需要鏈接PHP,而PHP又需要鏈接MySQL,Redis等。為了讓容器間互相鏈接更加容易管理,Docker官方推薦使用Docker-Compose完成這些操作。

      用一行指令完成安裝

      pip install -U docker-compose
      

      然后在Docker項目的根目錄下準備一個docker-compose.yml文件,內(nèi)容為:

      nginx:
          build: ./nginx
          ports:
            - "80:80"
          links:
            - "php"
          volumes:
            - ~/opt:/opt
      
      php:
          build: ./php
          ports:
            - "9000:9000"
          links:
            - "mysql"
            - "redis"
          volumes:
            - ~/opt:/opt
      
      mysql:
          build: ./mysql
          ports:
            - "3306:3306"
          volumes:
            - ~/opt/data/mysql:/var/lib/mysql
          environment:
            MYSQL_ROOT_PASSWORD: 123456
      
      redis:
          build: ./redis
          ports:
            - "6379:6379"
      
      

      然后運行docker-compose up,就完成了所有的端口綁定、掛載、鏈接操作。

      更復雜的實例

      上面是一個標準PHP項目在Docker環(huán)境下的演進過程,實際項目中一般會集成更多更復雜的服務,但上述基本步驟仍然可以通用。比如EvaEngine/Dockerfiles是為了運行我的開源項目EvaEngine準備的基于Docker的開發(fā)環(huán)境,EvaEngine依賴了隊列服務Gearman,緩存服務Memcache、Redis,前端構(gòu)建工具Gulp、Bower,后端Cli工具Composer、PHPUnit等。具體實現(xiàn)方式可以自行閱讀代碼。

      經(jīng)過團隊實踐,原本大概需要1天時間的環(huán)境安裝,切換到Docker后只需要運行10余條指令,時間也大幅縮短到3小時以內(nèi)(大部分時間是在等待下載),最重要的是Docker所構(gòu)建的環(huán)境都是100%一致的,不會有人為失誤引起的問題。未來我們會進一步將Docker應用到CI以及生產(chǎn)環(huán)境中。

      本文首發(fā)于我在臥龍閣的專欄PHP與創(chuàng)業(yè)的那些事兒,轉(zhuǎn)載請保留。

        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多