docker_04days

拆尼斯、帕丁顿發表於2024-03-15

docker-compose介紹

# 使用了docker 面臨一個比較大的問題,如果一個djagno專案,使用mysql,redis,不要一次性把所有服務都放到一個容器中,每個服務一個容器,批次的管理多個容器,比較難以操作,於是有了docker-compose

# 批次管理,操作docker容器的軟體---》只在單機

# Docker Compose是一個能一次性定義和管理多個Docker容器的工具,單機容器編排【定義和管理】


# 多機容器編排
    docker swarm:用的不多
    k8s
    
# Docker Compose概念
    Compose中定義和啟動的每一個容器都相當於一個服務(service)
    Compose中能定義和啟動多個服務,且它們之間通常具有協同關係
    管理方式:
    使用YAML檔案來配置我們應用程式的服務。
    使用單個命令(docker-compose up),就可以建立並啟動配置檔案中配置的所有服務。
 

# 安裝docker-compose (可執行檔案,放在了github上,下載下來即可,速度很慢)
# https://github.com/docker/compose/releases
wget https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-linux-x86_64
cp ./docker-compose-linux-x86_64 /usr/local/bin/docker-compose # 想在任意路徑下執行docker-compose都有相應----》需要把可執行檔案放到環境變數所在的目錄下
chmod +x /usr/local/bin/docker-compose

# rwx    rwx    rwx
  屬主   屬組    其他
  r:read 讀許可權 
  w:wirte 寫許可權
  x :執行許可權

 屬主   屬組  所有人
 chmod 777 檔名
 chmod +x 

    
  rwx  r-x   -wx
  101  101   101 
  chmod 555 檔名
    
    
    
# 以後在任意位置敲docker-compose都可以


# epel源
    -yum install centos倉庫下載
    -軟體不全
    -epel源 擴充套件源 
        nginx
        redis
        python



# 常用命令
# 啟動管理容器
docker-compose up  # 會自動搜尋當前路徑下的 docker-compose.yml檔案
docker-compose -f 指定檔案 up
docker-compose up -d  # 後臺執行,一般我們看日誌輸出,不用這個

docker-compose stop  # 停止,不會刪除容器和映象
docker-compose down # 停止,並刪除關聯的容器
docker-compose start  # 啟動yml檔案管理的容器
docker-compose ps    # 正在執行的容器
docker-compose images # docker-compose管理的映象

docker-compose exec yml檔案中寫的service /bin/bash  # 進入到容器內

docker-compose up -d --build # 啟動容器但是重新構建映象,基於重新構建的映象啟動


#####  如果你沒裝docker#######
# 配置yum倉庫
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 下載 docker-ce  docker-ce-cli docker-compose-plugin
sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

docker-compose部署flask案例

# flask 專案,使用redis服務---》2個容器
flask 專案容器
redis容器

新建flask專案 app.py

from flask import Flask
from redis import Redis
import os

app = Flask(__name__)
# redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)
redis = Redis(host='redis', port=6379) # 容器的主機名---》flask容器和redis容器是能ping通的,可以透過ip ping 也可以透過主機名ping

@app.route('/')
def hello(): 
    redis.incr('hits')
    return '你好! 檢視 %s 次\n' % (redis.get('hits'))


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)
-----------------------------------------------------
from flask import Flask
from redis import Redis
import os

app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello(): 
    redis.incr('hits')
    return '你好! 檢視 %s 次\n' % (redis.get('hits'))


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

編寫Dockerfile--》用於構建flask專案的映象

FROM python:3.10
WORKDIR /app
COPY . /app
RUN pip install flask redis -i https://pypi.tuna.tsinghua.edu.cn/simple
EXPOSE 5000
CMD [ "python", "app.py" ]

# 構建出映象---》一會統一使用 docker-compose構建

編寫docker-compose的yaml檔案 docker-compose.yml

version: "3"
services:
  redis:
    image: redis
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 8080:5000
    environment:
      REDIS_HOST: redis
          

docker-compose啟動

# 如果redis服務的名字叫redis,我在web服務中(容器中),根據redis名字就能拿到容器
    ping redis
    
    # 進入到了web,ping redis
    # 安裝ping命令,
    apt-get update
    apt-get install inetutils-ping
    ping redis
    
    
# 一鍵部署:redis,和flask ,每個都在一個容器中
docker-compose up
    -構建flask映象
    -reids映象如果不存在,就會拉取
    -啟動flask
    -啟動redis

docker-cmopose部署路飛

# 一臺伺服器:
    -python3.8 環境 djagno +uwsgi+程式碼
    -nginx軟體
    -mysql 5.7
    -redis 
    
# 每個都做成一個容器
    -djagno專案容器:python3.8 構建的django,專案依賴模組,uwsgi,程式碼
    -nginx容器:目錄對映,對映到宿主機,代理vue前端,編譯後的靜態檔案
    -mysql 容器:建立,創使用者,密碼,luffy庫
    -redis 容器,跑起來即可
    
    
# docker-compose yml檔案配置,一鍵啟動
    -git clone https://gitee.com/liuqingzheng/luffy.git
    -目錄結構
    luffy
        luffy_api  # 後臺專案
            Dockerfile
        luffycity  # 前臺專案
        docker_compose_files # 放資料的資料夾
        
        docker-compose.yml #ymal檔案
    -docker-compose.yml內容
    -Dockefile 檔案
    - 修改前端連結後臺的地址:luffycity/src/access/xx.js
    -編譯:npm run build
    
    -提交到git
    
    -要部署的伺服器:git clone https://gitee.com/liuqingzheng/luffy.git
    -docker,docker-compose裝好
    -docker-compose up
    -訪問宿主機的 80

專案目錄結構

luffy
    -docker_compose_files  # nginx有自己的配置檔案,redis自己的配置,mysql的配置
        nginx # 資料夾
        redis # 資料夾
        mysql.env#配置檔案
    -luffy_api  # 原來路飛後端專案
        -Dockerfile
        -luffy.ini  # luffy.xml uwsgi的配置檔案
    -luffycity  # 前端專案
    
    -docker-compose.yml  # docker-compose的配置檔案
    

# 把luffycity/dist 資料夾刪除
# 把\luffy\luffycity\src\assets\js\settings.js後端地址改成上線地址(伺服器地址)
# 來到前端路徑下:luffy\luffycity
cnpm install  安裝依賴

# 編譯,在\luffy\luffycity\dist資料夾
npm run build

# 提交到git上


# 在部署的機器上,git clone 下來
# 進入到專案目錄
docker-compose up

luffy_api/Dockerfile--->構建uwsgi+django

FROM python:3.8
MAINTAINER lqz
WORKDIR /soft
COPY ./requestment.txt /soft/requestment.txt
RUN pip install -r requestment.txt -i https://pypi.doubanio.com/simple
#CMD ["uwsgi", "-x", "./luffy.xml"]
CMD ["uwsgi", "./luffy.ini"]
#CMD ["python", "manage_pro.py", "runserver"]

docker-compose.yml

version: "3"

services:
  nginx:
    image: nginx
    container_name: luffy_nginx
    ports:
      - "80:80"
      - "8000:8000"
    restart: always
    volumes:
      - ./luffycity/dist:/var/www/html
      - ./docker_compose_files/nginx:/etc/nginx/conf.d
    depends_on:
      - django
    networks:
      - web

  django:
    build:
      context: ./luffy_api
      dockerfile: Dockerfile
    container_name: luffy_django
#    command: python manage_pro.py makemigrations && python manage_pro.py migrate && uwsgi ./luffy.ini
    restart: always
    ports:
      - "8080:8080"
    volumes:
      - ./luffy_api:/soft
    environment:
      - TZ=Asia/Shanghai
    depends_on:
      - mysql
      - redis
    networks:
      - web
  redis:
    image: redis:6.0-alpine
    container_name: luffy_redis
    ports:
      - "6379:6379"
    volumes:
      - ./docker_compose_files/redis/data:/data
      - ./docker_compose_files/redis/redis.conf:/etc/redis/redis.conf
    command: redis-server /etc/redis/redis.conf
    networks:
      - web
  mysql:
    image: mysql:5.7
    container_name: luffy_mysql
    restart: always
    ports:
      - "3306:3306"
    env_file:
      - ./docker_compose_files/mysql.env
    volumes:
      - ./docker_compose_files/mysql/data:/var/lib/mysql
      - ./docker_compose_files/mysql/logs:/var/log/mysql
      - ./docker_compose_files/mysql/conf:/etc/mysql/conf.d
    networks:
      - web

networks:
  web:

一鍵部署

docker-compose up   

 

補充

django中實現事務的幾種方式

# https://zhuanlan.zhihu.com/p/622987268

Django是支援事務操作的,它的預設事務行為是自動提交,
具體表現形式為:每次資料庫操作(比如呼叫save()方法)會立即被提交到資料庫中。
但是如果你希望把連續的SQL操作包裹在一個事務裡,就需要手動開啟事務


# 根據粒度不同,三種
######## 全域性##########
    -全域性,每次請求在一個事務中,粒度太大,事務時間很長
    DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.mysql',
         'NAME': 'lqz',
         'HOST': '127.0.0.1',
         'PORT': '3306',
         'USER': 'lqz',
         'PASSWORD': 'lqz123',
          #全域性開啟事務,繫結的是http請求響應整個過程
         'ATOMIC_REQUESTS': True, 
         }
    }
# 區域性禁用全域性事務
from django.db import transaction
# 區域性禁用事務
@transaction.non_atomic_requests
def seckill(request):
    return HttpResponse('秒殺成功')
 
# 多資料庫,用default的檢視不受事務控制
@transaction.non_atomic_requests(using='default')
def seckill(request):
    return HttpResponse('秒殺成功')



####### 檢視開啟事務##############
# fbv開啟
from django.db import transaction
@transaction.atomic
def seckill(request):
    return HttpResponse('秒殺成功')


# cbv開啟
from django.db import transaction
from rest_framework.views import APIView
class SeckillAPIView(APIView):
    @transaction.atomic
    def post(self, request):
        pass
    
    
    
################ 區域性使用事務#####################
from django.db import transaction
def seckill(request):
    with transaction.atomic():
        save()
        update()
    return HttpResponse('秒殺成功')

事物的回滾和儲存點

# 1 普通事務操作(手動操作)
transaction.atomic()  # 開啟事務
transaction.commit()  # 提交事務
transaction.rollback() # 回滾事務

# 2 可以使用上下文管理器來控制(自動操作)
with transaction.atomic():  # 自動提交和回滾
    
    
    
    
# 3 儲存點
    -開啟事務
    幹了點事
    設定儲存點1
    幹了點事
    設定一個儲存點2
    幹了點事
    
    回滾到幹完第二個事,回滾到儲存點2
    
    
'''
在事務操作中,我們還會經常顯式地設定儲存點(savepoint)
一旦發生異常或錯誤,我們使用savepoint_rollback方法讓程式回滾到指定的儲存點
如果沒有問題,就使用savepoint_commit方法提交事務
'''

from .models import Book
from django.db import transaction
def seckill(request):
    with transaction.atomic():
        # 設定回滾點,一定要開啟事務
        sid = transaction.savepoint()
        print(sid)
        try:
            book = Book.objects.get(pk=1)
            book.name = '紅樓夢'
            book.save()
        except Exception as e:
            # 如發生異常,回滾到指定地方
            transaction.savepoint_rollback(sid)
            print('出異常了,回滾')
        # 如果沒有異常,顯式地提交一次事務
        transaction.savepoint_commit(sid)
    return HttpResponse('秒殺成功')
transaction.atomic()  # 開啟事務
sid = transaction.savepoint() # 設定儲存點
transaction.savepoint_rollback(sid) # 回滾到儲存點
transaction.savepoint_commit(sid) #提交儲存點

事務提交後,執行某個回撥函式

# 有的時候我們希望當前事務提交後立即執行額外的任務,比如客戶下訂單後立即郵件通知賣家
###### 案例一##################
def send_email():
    print('傳送郵件給賣家了')
def seckill(request):
    with transaction.atomic():
        # 設定回滾點,一定要開啟事務
        sid = transaction.savepoint()
        print(sid)
        try:
            book = Book.objects.get(pk=1)
            book.count = book.count-1
            book.save()
        except Exception as e:
            # 如發生異常,回滾到指定地方
            transaction.savepoint_rollback(sid)
        else:
            transaction.savepoint_commit(sid)
            #transaction.on_commit(send_email)
            transaction.on_commit(lambda: send_sms.delay('1898288322'))
    return HttpResponse('秒殺成功')


##### 案例二:celery中使用###
transaction.on_commit(lambda: send_sms.delay('1898288322'))

django實現悲觀鎖樂觀鎖案例

# 線上賣圖書
    -圖書表  圖書名字,圖書價格,庫存欄位
    -訂單表: 訂單id,訂單名字
    
# 表準備
    class Book(models.Model):
        name = models.CharField(max_length=32)
        price = models.IntegerField()  #
        count = models.SmallIntegerField(verbose_name='庫存')
    class Order(models.Model):
        order_id = models.CharField(max_length=64)
        order_name = models.CharField(max_length=32)
        
# 使用mysql
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'lqz',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'lqz',
        'PASSWORD': '123',
    }
}

# 建立lqz資料庫

原生mysql悲觀鎖

begin; # 開啟事務

select * from goods where id = 1 for update;  # 行鎖

# order表中加資料

update goods set stock = stock - 1 where id = 1; # 更新

commit; #提交事務

orm實現上述

#1  使用悲觀鎖實現下單
@transaction.atomic  # 整個過程在一個事物中---》改兩個表:book表減庫存,訂單表生成記錄
def seckill(request):
    # 鎖住查詢到的book物件,直到事務結束
    sid = transaction.savepoint() # 儲存點
    # 悲觀鎖: select_for_update()
    # 加鎖了--》行鎖還是表鎖? 分情況,都有可能
    #
    book = Book.objects.select_for_update().filter(pk=1).first()  # 加悲觀鎖,行鎖,鎖住當前行
    if book.count > 0:
        print('庫存可以,下單')
        # 訂單表插入一條
        Order.objects.create(order_id=str(datetime.datetime.now()), order_name='測試訂單')
        # 庫存-1,扣減的時候,判斷庫存是不是上面查出來的庫存,如果不是,就回滾
        time.sleep(random.randint(1, 4))  # 模擬延遲
        book.count=book.count-1
        book.save()
        transaction.savepoint_commit(sid)  # 提交,釋放行鎖
        return HttpResponse('秒殺成功')
    else:
        transaction.savepoint_rollback(sid) #回滾,釋放行鎖
        return HttpResponse('庫存不足,秒殺失敗')

樂觀鎖秒殺--》庫存還有,有的人就沒成功

# 2 樂觀鎖秒殺--普通版
@transaction.atomic
def seckill(request):
    # 鎖住查詢到的book物件,直到事務結束
    sid = transaction.savepoint()
    book = Book.objects.filter(pk=1).first()  # 沒加鎖
    count = book.count
    print('現在的庫存為:%s' % count)
    if book.count > 0:
        print('庫存可以,下單')
        Order.objects.create(order_id=str(datetime.datetime.now()), order_name='測試訂單-樂觀鎖')
        # 庫存-1,扣減的時候,判斷庫存是不是上面查出來的庫存,如果不是,就回滾
        # time.sleep(random.randint(1, 4))  # 模擬延遲
        res = Book.objects.filter(pk=1, count=count).update(count=count - 1)
        if res >= 1:  # 表示修改成功
            transaction.savepoint_commit(sid)
            return HttpResponse('秒殺成功')
        else:  # 修改不成功,回滾
            transaction.savepoint_rollback(sid)
            return HttpResponse('被別人改了,回滾,秒殺失敗')

    else:
        transaction.savepoint_rollback(sid)
        return HttpResponse('庫存不足,秒殺失敗')