在一般日常,應該有些是需要定時去執行功能的程式,
但往往程式如果一多,光想著該佈署在哪些機器上,跟安裝所需的執行環境.其實也是件很繁瑣的事.
但透過Docker的機制,就可以很方便的將程式跟執行環境在包在一起.省去一些繁瑣的建置時間
好讓自己可以偷懶XD 笑~~~~
此篇文章是小弟在執行上遇到的一些問題,
稍做記事分享,如果各位前輩有更好的方法.
也請多多指導!
需求假設:需要有一支背景程式,
定時一分鐘定時去擷取中央氣象局網頁內容,
並在Docker內執行.
定時一分鐘定時去擷取中央氣象局網頁內容,
並在Docker內執行.
開發環境: Linux Mint 19.1
Docker 18.06.1-ce
Net Core 2.2
第一步.
先來寫個Dockerfile來打包自己的程式跟執行環境.
# Stage 1. dotnet 程式編譯環境SDK
FROM mcr.microsoft.com/dotnet/core/sdk:2.2 as build_env
# 參數
ARG DOTNET_BUILD_CONFIG=Release
# 複製原始碼
#左邊的./代表要打包的映像檔內根目錄位置,./Parseweather右邊為專案檔內資料夾的位置
COPY ./ ./Parseweather
# 移至要建置的專案
WORKDIR ./Parseweather
# 編譯發布
RUN dotnet publish -o /app -c $DOTNET_BUILD_CONFIG
#移至編譯過後指定的的資料夾
WORKDIR /app
# Stage 2. 建置程式執行環境runtime
FROM mcr.microsoft.com/dotnet/core/runtime:2.2 as runtime
# 從Stage 1 複製編譯過後的檔案至執行環境
COPY --from=build_env /app /app
# 切換執行位置
WORKDIR /app
#執行
ENTRYPOINT ["dotnet", "Parseweather.dll"]
程式目錄結構
在此小弟就不在多贅述Quartz跟AngleSharp會在最後附上其他前輩所寫的參考連結
Program.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using Parseweather.Jobs;
using Quartz;
using Quartz.Impl;
namespace Parseweather
{
internal class Program
{
private static async Task Main(string[] args)
{
Console.WriteLine("Console Start");
var schedulerFactory = new StdSchedulerFactory();
var schedule = await schedulerFactory.GetScheduler();
var job = JobBuilder.Create<ParseWeather>()
.WithIdentity("ParseWeatherJob")
.Build();
var trigger = TriggerBuilder.Create()
.WithCronSchedule("0 0/1 * * * ?") // 每一分鐘觸發一次。
.WithIdentity("ParseWeatherTrigger")
.Build();
// 把工作加入排程
await schedule.ScheduleJob(job, trigger);
// 啟動排程器
await schedule.Start();
SpinWait.SpinUntil(() => false);
}
}
}
這邊比較特別的是透過的SpinWait.SpinUntil的方式讓他等待.
因為在Docker 容器內起來時,如果沒讓主要Thread等待,
只要一啟動容器程式跑完就會離開.
並不會有等待的動作
如下圖
接下擷取網頁內容的部份
ParseWeather.cs
using System;
using System.Linq;
using System.Threading.Tasks;
using AngleSharp;
using AngleSharp.Dom;
using Parseweather.Models;
using Quartz;
namespace Parseweather.Jobs
{
public class ParseWeather : IJob
{
public async Task Execute(IJobExecutionContext context)
{
Console.WriteLine($"job start {DateTime.Now:yyyyMMdd-HHmm}");
var config = Configuration.Default.WithDefaultLoader();
const string url = "https://www.cwb.gov.tw/V7/forecast/f_index.htm";
using (var dom = await BrowsingContext.New(config).OpenAsync(url))
{
//取出網頁上所有縣市ID
var parseCityIds = dom
.QuerySelectorAll("body > div > div > div > table > tbody > tr")
.Select(s => s.GetAttribute("id")).Where(w => w != null);
//針對id向下擷取各個內容
var result = parseCityIds.Select(item => new WeatherResponse
{
City = dom.QuerySelector($"#{item} > td:nth-child(1)").Text(),
Temperature = dom.QuerySelector($"#{item} > td:nth-child(2) > a").Text(),
RainPercent = dom.QuerySelector($"#{item} > td:nth-child(3) > a").Text(),
ImageUrl = dom.QuerySelector($"#{item} > td:nth-child(4) > a > div > img").GetAttribute("src"),
Explain = dom.QuerySelector($"#{item} > td:nth-child(4) > a > div > img").GetAttribute("title")
});
foreach (var item in result)
Console.WriteLine(
$"城市:{item.City} 溫度:{item.Temperature} 降雨機率:{item.RainPercent} 圖片位置:{item.ImageUrl} 說明:{item.Explain}{Environment.NewLine}");
}
Console.WriteLine($"job Leave {DateTime.Now:yyyyMMdd-HHmm}");
}
}
}
執行Docker建置Image的動作嚕
docker build -t {optional-name}/{optional-image-name}:{tag} .
看看建置的Image是否有出現在列表中
docker images {optional-name}/{optional-image-name}:{tag}
#or
docker images
啟動來~試一試~~~~
#前景執行
docker run -t --name {optione_name} {optional-name}/{optional-image-name}:{tag}
#OR
#背景執行
docker run -d --name {optione_name} {optional-name}/{optional-image-name}:{tag}
確定每分鐘有執行擷取網頁的動作
確認容器啟動的狀態
docker ps -a
有了Docker的存在,在程式的執行跟建置上提供了不少的便利,但不是偷懶XD
在此附上原始程式碼
請點我
參考連結:
Prepare a .Net Core Console App for Docker
The AngleSharp Project • Documentation
ASP.NET 程式中的背景工作 (3) - 使用 Quartz.NET
[C#.NET][Thread] 善用 SpinWait 處理 執行緒空轉 以利提昇效能