Well, in this case, the getCreatedAtAttribute accessor is only applied whenever you retrieve a model from the database. In the scopeByPeriod method you're still working on database level where the timezone is still used based on what is stored in the database.
The best solution here is to convert the $start and $end dates from Europe/Moscow to UTC when comparing them in the query.
public function scopeByPeriod(Builder $query, $start, $end)
{
$start = Carbon::parse($start)->setTimezone('UTC');
$end = Carbon::parse($end)->setTimezone('UTC');
return $query
->whereDate('created_at', '>=', $start)
->whereDate('created_at', '<=', $end);
}
Another solution is using convert_tz in a raw query but that also makes it more complicated.
The general idea when working with timezones is that you should stick to the default (UTC) and convert everything to UTC when working with data. Only convert it back to the correct timezone when displaying the value. This way you have the least headaches with timezones ;)