private – wyizolowana sieć, bez dostępu do sieci hosta
internal – wewnętrzna wirtualna sieć, do której dostęp może mieć komputer wirtualny, jak i system hosta, co za tym idzie, można udostępnić Internet
external – maszyna wirtualna „widzi” tę samą sieć, co host
Jeśli chcesz, aby maszyna wirtualna miała dostęp do Internetu, to przez eliminację odpada sieć private. Można by skorzystać z sieci external, ale wtedy jest delikatny problem z adresem IP komputera wirtualnego, bo jest on przyznawany przez DHCP, a możesz preferować jednak stały adres IP. Ewentualne skonfigurowanie statycznego adresu IP na maszynie wirtualnej może doprowadzić do konfliktów, a tego byśmy nie chcieli. I tak zostaje sieć internal.
No to po kolei:
Otwórz managera Hyper-V i w Virtual Switch Manager utwórz sieć typu internal. Możemy ją nazwać np. InternalSwitch. Można to też zrobić poleceniem PowerShell:
Na komputerze hosta w PowerShell uruchomionym jako administrator uruchom kolejno polecenia, które skonfigurują tę maszynę do NAT:
Przypisanie do interfejsu hosta odpowiadającego za komunikację z siecią wewnętrzną stałego adresu IP (tutaj 192.168.0.1). Jeśli nie lubisz PowerShella, można to też wyklikać
Konfiguracja wewnętrznej sieci do NAT. Tutaj trzeba użyć PowerShella, lub ewentualnie dodać do hosta rolę RRAS (Routing and Remote Access Server), ale to dość dużo pracy w porównaniu do jednej komendy
Network manager to sprytna bestia. Można dla jednego interfejsu sieciowego zdefiniowac np. polaczenie net-static, ktore przypisuje temu interfejsowi adres statyczny oraz polaczenie net-dynamic, ktore przydziela interfejsowi adres z DHCP. W danej chwili może być aktywne tylko jedno połącznie, ale w ramach potrzeb można się przełączyć z jednej konfiguracji do drugiej. Zwykle jedno połączenie odpowiada jednemu interfejsowi, ale… warto wiedzieć, że się da 🙂
Apache Spark (spark) pracuje na obiektach data frame podobnych do tych z Pandas, ale zoptymalizowanych do pracy z Spark engine. Tutaj wczytujemy (read) plik csv (format), który w pierwszym wierszu ma nagłówek (option) z pliku (load):
df = spark.read.format("csv").option("header","true").load("Files/orders/2019.csv")
# df now is a Spark DataFrame containing CSV data from "Files/orders/2019.csv".
display(df)
Plik CSV może nie mieć nagłówka i wtedy po jego wczytaniu kolumny będą miały nazwy _c01, _c02, … Dlatego można „nałożyć” na plik CSV strukturę. W takim przypadku będziemy pracować z czymś, co przypomina tabelę SQL. Rzeczywiście w spark mamy definicje typów SQL. Tutaj wczytujemy bibliotekę pyspark.sql.types, a następnie tworzymy strukturę (StructType), która składa się z kolumn (StructField), a te kolumny mogą być różnego typu, np.: StringType, IntegerType, DateType, FloatType. Zmienia się też sposób wczytania danych. Nadal czytamy (read) dane w postaci csv (format), ale teraz nadajemy im strukturę (schema), a same dane pobieramy z pliku (load).
W oparciu o jeden data frame można tworzyć kolejne. Można np. wybrać tylko niektóre kolumny wymieniając je w nawiasach kwadratowych. Data frame ma automatycznie kilka przydatnych funkcji. Np. count() policzy ile wierszy ma data frame, distinct() zwróci tylko wiersze unikalne, a co za tym idzie distinct().count() policzy ile wierszy jest unikalnych:
Zamiast wymieniania nazw kolumn w nawiasie kwadratowym, można też użyć jawnej funkcji select:
customers = df.select("CustomerName", "Email")
Data frame ma metodę where, która pozwala odfiltrować wiersze spełniające określone warunki. Warunek definiuje się odwołując się do kolumn danych. Np. tutaj filtr wskazuje, że należy wyświetlić te wiersze, które w kolumnie Item mają określony tekst:
Dane z data frame można grupować. Wystarczy wskazać, które kolumny zawierają dane „po których” ma się odbywać grupowanie, oraz wybrać funkcję agregującą:
Spark może się posługiwać funkcjami występującymi w SQL. Te funkcje znajdują się w module pyspark.sql.functions. Dzięki temu można uruchomić funkcję, np. tutaj year, przekazać do niej kolumnę (col), oraz nadać jej alias (alias). Dodatkowo dane można posortować (orderBy):
from pyspark.sql.functions import *yearlySales = df.select(year(col("OrderDate")).alias("Year")).groupBy("Year").count().orderBy("Year")display(yearlySales)
Podczas transformacji można wyznaczać nowe kolumny, których wartość jest zbudowana w oparciu o istniejące kolumny. W tym celu korzysta się z withColumn, której argumentem jest nazwa nowo tworzonej kolumny oraz wyrażenie wyznaczające wartość tej kolumny. W wyrażeniach można używać funkcji takich, jak: year, month, split, getItem. Kolumny można eliminować lub zmieniać ich kolejność korzystając z metody select lub z nawiasów kwadratowych, w których zostaną wymienione we właściwej kolejności tylko te kolumny, które mają pozostać.
Przetworzone dane można „zwrócić” zapisując je (write) do pliku. Można zdecydować, że plik ma być nadpisany (mode), oraz wskazać na format tworzonego pliku (parquet).
transformed_df.write.mode("overwrite").parquet('Files/transformed_data/orders')
print ("Transformed data saved!")
Dane zapisane w formacie parquet można wczytać, wskazując na format i lokalizację. Słowo order w ścieżce oznacza nazwę katalogu. W tym katalogu w formacie parquet są przechowywane dane.
Podczas zapisu danych, można je partycjonować. Tutaj we wskazanym w metodzie parquet katalogu powstaną podkatalogi Year i Month wskazujące na konkretną partycję, np. Year=2022/Month=1:
orders_df.write.partitionBy("Year","Month").mode("overwrite").parquet("Files/partitioned_data")
print ("Transformed data saved!")
Następnie w intuicyjny sposób można wczytać tylko część danych:
Jeśli preferujesz pracować z danymi z wykorzystaniem SQL, to możesz zapisać dane „jako tabela” (saveAsTable). Opisanie danych strukturą SQL może być wykonane w wewnętrznym metastore Sparka (tzw managed table) lub może odnosić się do pliku, który znajduje się w data lake (external table). W tej metodzie nie podajemy pełnej ścieżki dostępu do pliku, co oznacza, że będzie on przechowywany w metastore. „delta” to jeden z dostępnych formatów, inne to: csv, parquet, avro itd. Te formaty mogą wprowadzać typowo bazo-danowe funkcjonalności jak transakcje, wersjonowanie rekordów itp.
Mając tak zdefiniowaną tabelę, można pisać do niej zapytania SQL i zapisywać wynik w data frame spark:
df = spark.sql("SELECT * FROM xyz_lakehouse.salesorders LIMIT 1000")
display(df)
Ponieważ spark pozwala na pracę z różnymi językami, to można przełączyć się do języka SQL komendą %%sql
%%sql
SELECT YEAR(OrderDate) AS OrderYear,
SUM((UnitPrice * Quantity) + Tax) AS GrossRevenue
FROM salesorders
GROUP BY YEAR(OrderDate)
ORDER BY OrderYear;
Przełączając się między widokami, można dokonać wstępnej analizy danych na automatycznie generowanych wykresach:
Z drugiej strony Spark to nadal notebook, w którym jest uruchamiany kod Python, dlatego można korzystać z licznych funkcjonalności, które występują w tym języku, np. można tworzyć wykresy z wykorzystaniem popularnego modułu matplotlib:
sqlQuery = "SELECT CAST(YEAR(OrderDate) AS CHAR(4)) AS OrderYear, \
SUM((UnitPrice * Quantity) + Tax) AS GrossRevenue \
FROM salesorders \
GROUP BY CAST(YEAR(OrderDate) AS CHAR(4)) \
ORDER BY OrderYear"
df_spark = spark.sql(sqlQuery)
df_spark.show()
from matplotlib import pyplot as pl
tdf_sales = df_spark.toPandas()
plt.bar(x=df_sales['OrderYear'], height=df_sales['GrossRevenue'])
plt.show()
Mając pythonowe funkcje możemy wpływać na to, jak taki wykres ma wyglądać:
from matplotlib import pyplot as plt
plt.clf()
plt.bar(x=df_sales['OrderYear'], height=df_sales['GrossRevenue'], color='orange')
plt.title('Revenue by Year')
plt.xlabel('Year')
plt.ylabel('Revenue')
plt.grid(color='#95a5a6', linestyle='--', linewidth=2, axis='y', alpha=0.7)
plt.xticks(rotation=45)
plt.show()
Matplotlib ma dużo możliwości pod względem formatowania wykresów i generalnie można korzystać ze wszystkich z nich. Tu np. tworzymy dwa wykresy w jednym:
from matplotlib import pyplot as plt
plt.clf()
fig, ax = plt.subplots(1, 2, figsize = (10,4))
ax[0].bar(x=df_sales['OrderYear'], height=df_sales['GrossRevenue'], color='orange')
ax[0].set_title('Revenue by Year')
yearly_counts = df_sales['OrderYear'].value_counts()
ax[1].pie(yearly_counts)
ax[1].set_title('Orders per Year')
ax[1].legend(yearly_counts.keys().tolist())
fig.suptitle('Sales Data')
plt.show()
Matplotlib jest uniwersalny, ale też pracochłonny. Równolegle powstają też inne biblioteki do tworzenia wykresów, jak np. seaborn:
Domyślnie, kiedy połączenie SSH pozostanie otwarte przez dłuższy czas bez żadnej aktywności, to serwer takie połączenie zamknie. Można skonfigurować to zachowanie:
Na serwerze w pliku /etc/ssh/sshd_config dodaj linie:
ClientAliveInterval 120
ClientAliveCountMax 720
ClientAliveInterval określa czas w sekundach między wysyłaniem przez serwer pakietów keep-alive do klienta. W tym przypadku jest to 120 sekund.
ClientAliveCountMax określa maksymalną liczbę pakietów keep-alive, które serwer wyśle bez odpowiedzi od klienta, zanim zakończy połączenie. W tym przypadku jest to 720, co daje łącznie 24 godziny (120 sekund * 720).
Po wykonaniu zmian zrestartuj SSHD:
sudo systemctl restart sshd
Potem zmiany można wykonać jeszcze na kliencie. Zmiany wprowadzamy w pliku sudo nano /etc/ssh/ssh_config:
Jeśli serwer korzysta z cloud-init, to zgodnie z instrukcją należy jeszcze wyłączyć automatyczą konfiguracje sieci, co robi się umieszczając w pliku /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
Czasami możesz chcieć uruchomić jakiś program z poziomu linii komend „Jako administrator”. Da się to zrobić za pomocą powershellowej komendy Start-Process, która ma parametr -verb. Jeśli przekażesz wartość parameteru „RunAs” to uruchamiany proces będzie działał „jako administrator”. Oto i przykład polecenia uruchamianego w linii komend, która już wcześniej została uruchomiona jako administrator:
pwsh -command "start-process pwsh -verb runas"
Jeśli chcesz się przekonać, czy rzeczywiście jesteś teraz administratorem użyj: